<?php
/**
 * -------------------------------------------------------------------------
 *
 * Module to provide specific routines for site templates.
 *
 * -------------------------------------------------------------------------
 *
 * This file implements the following routines:
 *
 *     helper->run          ( $filename            )  -->  to check a template file exists 
 *     helperGetStorage     (                      )  -->  to return a root projects directory
 *     helperListProjects   (                      )  -->  to return a list of developed projects
 *     helperOpenProject    ( $directory           )  -->  to open a project
 *     helperRemoveProject  ( $directory           )  -->  to remove a project by its directory name
 *     helperRemoveFile     ( $filename            )  -->  to remove a file from the current project
 *     helperCreateProject  ( $directory           )  -->  to create a new project with the same name as the directory
 *     helperCreateFile     ( $filename, $content  )  -->  to create a new file in the current project
 *     helperUploadFile     ( $filename, $uploaded )  -->  to upload a new file in the current project
 *     helperCloseProject   (                      )  -->  to close the current project
 *     helperFilePattern    ( $plusViewer          )  -->  to return a RegExp pattern for file names
 *     helperFileExtensions ( $plusViewer          )  -->  to return a part of RegExp pattern for file extensions
 *     helperDisplayMenu    ( $map, $asTool        )  -->  to render a link map as a menu or toolbar
 *     helperLoginUser      ( $login, $pass        )  -->  to log in an user with its login and password
 *     helperLogoutUser     (                      )  -->  to log out the current user
 *     helperHasProduction  (                      )  -->  to check if the site is not in demo mode
 *     helperHasAdmin       (                      )  -->  to check if the current user is an admin
 *     helperHasProject     (                      )  -->  to check if the project is already open
 *     helperHasChanges     (                      )  -->  to check if the project files have been modified
 *     helperHasFile        ( $filename            )  -->  to check a project file exists
 *
 * Please note that we prefix all of these routines with the word "helper"
 * to avoid unexpected naming conflicts and to make the routine's location
 * identifiable by its name.
 *
 * -------------------------------------------------------------------------
 *
 * @package    MimimiFramework
 * @subpackage Examples / IDE skeleton
 * @license    GPL-2.0
 *             https://opensource.org/license/gpl-2-0/
 * @copyright  2022 MiMiMi Community
 *             https://mimimi.software/
 *
 * -------------------------------------------------------------------------
 */

    mimimiInclude ( 'Helper/Helper.php' );

    class MyMimimiHelper extends MimimiHelper {

        /**
         * -----------------------------------------------------------------
         *
         * Checks if a template file exists.
         *
         * -----------------------------------------------------------------
         */

        public function run ( $filename = '' ) {
            $root = mimimiBasePath ( );
            $file = $root . MIMIMI_APP_FOLDER . 'Themes/' . MIMIMI_APP_THEME . '/' . $filename;
            return file_exists ( $file )
                && is_file     ( $file )
                && is_readable ( $file );
        }
    };

    /**
     * ---------------------------------------------------------------------
     *
     * Returns the root directory to store projects.
     *
     * ---------------------------------------------------------------------
     */

    function helperGetStorage ( ) {
        return MIMIMI_APP_FOLDER . 'Projects/';
    };

    /**
     * ---------------------------------------------------------------------
     *
     * Returns a list of projects.
     *
     * ---------------------------------------------------------------------
     *
     * Project is a directory and its TPL files located inside the "Projects"
     * module. To get a list of projects, we just use "mimimiFolder" routine
     * to read the names of all the directories in that module's directory.
     * This list is a flat array of names indexed by lowercase directory
     * name.
     *
     * Important! We do not check whether the listed directories contain
     * any TPL file within themselves. That is, this routine will return
     * even unfinished projects.
     *
     * ---------------------------------------------------------------------
     */

    function helperListProjects ( ) {
        return mimimiFolders (
                   helperGetStorage ( )
               );
    };

    /**
     * ---------------------------------------------------------------------
     *
     * Opens a project.
     *
     * ---------------------------------------------------------------------
     *
     * First, we use the "mimimiSafePath" routine to check that the required
     * directory name is not empty and valid.
     *
     * Then we save the minimal project data in the session under the
     * "userProject" parameter. Initially it has an empty "files" element.
     * FALSE means that the project files have not been modified yet.
     *
     * Next, we scan that directory to fill the element "files" using a
     * callback function. This function matches the extension of each
     * scanned file and stores it in the element "files" as editable (TRUE)
     * or uneditable (FALSE).
     *
     * ---------------------------------------------------------------------
     */

    function helperOpenProject ( $directory ) {
        if ( mimimiSafePath ( $directory ) ) {
            global $app;
                   $app->session->set ( 'userProject', [ 'dir'      => $directory,
                                                         'modified' => FALSE,
                                                         'files'    => [ ] ] );
            $callback = function ( $absoluteFile, $relativeFile, $isDirectory ) {
                            if ( ! $isDirectory ) {
                                $editable = NULL;
                                global $app;
                                if      ( preg_match ( '~(' . $app->projects->editorExtensions . ')$~u', $relativeFile ) ) $editable = TRUE;
                                else if ( preg_match ( '~(' . $app->projects->viewerExtensions . ')$~u', $relativeFile ) ) $editable = FALSE;
                                if ( is_bool ( $editable ) ) {
                                    $segment = '[^/]+';
                                    $pattern = '~^' . $segment . '/'            // <-- it is directory of your app
                                                    . $segment . '/'            // <-- it is "Projects" directory
                                                    . $segment . '/' . '~u';    // <-- it is your project directory
                                    $file = preg_replace ( $pattern, '', $relativeFile );
                                    $data = $app->session->get ( 'userProject' );
                                    $data[ 'files' ][ $file ] = $editable;
                                    $app->session->set ( 'userProject', $data );
                                }
                            }
                        };
            mimimiScan ( helperGetStorage ( ) . $directory, $callback, TRUE );
        }
    };

    /**
     * ---------------------------------------------------------------------
     *
     * Removes a project.
     *
     * ---------------------------------------------------------------------
     */

    function helperRemoveProject ( $directory ) {
        if ( mimimiSafePath ( $directory ) ) {
            mimimiClean ( helperGetStorage ( ) . $directory   );
            @ rmdir     ( mimimiBasePath (       $directory ) );
        }
    };

    /**
     * ---------------------------------------------------------------------
     *
     * Removes a file from the current project.
     *
     * ---------------------------------------------------------------------
     *
     * First, we use the "helperHasProject" routine to check that the
     * project data contains an element "dir" with a non-empty directory
     * name.
     *
     * Then we use the "helperHasFile" routine to check that those data
     * also contains an entry about the requested file name inside an
     * element "files".
     *
     * If these checks are successful, we remove the entry of that file
     * from the project data and save the result back in the session under
     * the "userProject" parameter.
     *
     * Next, we remove the file itself, and then remove its directory branch
     * if it is empty.
     *
     * ---------------------------------------------------------------------
     */

    function helperRemoveFile ( $filename ) {
        if ( $data = helperHasProject ( ) ) {
            if ( $index = helperHasFile ( $filename ) ) {

                unset ( $data[ 'files' ][ $index ] );
                global $app;
                       $app->session->set ( 'userProject', $data );

                $filename = helperGetStorage ( ) . $data[ 'dir' ] . '/' . $filename;
                $filename = mimimiBasePath ( $filename, TRUE );
                @ unlink ( $filename );

                $index = count ( preg_split ( '~/~u', $index ) );
                while ( $index > 1 ) {
                    $index--;
                    $filename = dirname ( $filename );
                    if ( ! @ rmdir ( $filename ) ) break;
                }
            }
        }
    };

    /**
     * ---------------------------------------------------------------------
     *
     * Creates a new project.
     *
     * ---------------------------------------------------------------------
     */

    function helperCreateProject ( $directory ) {
        if ( mimimiSafePath ( $directory ) ) {
            helperRemoveProject ( $directory );
            global $app;
                   $app->session->set ( 'userProject', [ 'dir'      => $directory,
                                                         'modified' => TRUE,
                                                         'files'    => [ ] ] );
            $crlf = "\r\n";
            helperCreateFile ( 'index.tpl', '<!DOCTYPE html>' . $crlf
                                          . '<html lang="en">' . $crlf
                                          . '    <head>' . $crlf
                                          . '        <base href="<?php printSiteUrl ( ) ?>projects/' . printValue ( mb_strtolower ( $directory, 'UTF-8' ), FALSE ) . '/">' . $crlf
                                          . '        <meta charset="UTF-8">' . $crlf
                                          . '        <meta name="viewport" content="width=device-width, initial-scale=1">' . $crlf
                                          . '        <meta name="robots" content="noindex, nofollow">' . $crlf
                                          . '        <title>Main Page</title>' . $crlf
                                          . '        <link rel="shortcut icon" href="favicon.svg" type="image/svg+xml">' . $crlf
                                          . '        <link rel="stylesheet"    href="style.css">' . $crlf
                                          . '    </head>' . $crlf
                                          . '    <body>' . $crlf
                                          . '        <p>Place the HTML markup for your project\'s main page here.</p>' . $crlf
                                          . '        <script src="script.js"></script>' . $crlf
                                          . '    </body>' . $crlf
                                          . '</html>' );

            helperCreateFile ( 'style.css', '/**' . $crlf
                                          . ' * -------------------------------------------------------------------------' . $crlf
                                          . ' *' . $crlf
                                          . ' * Styles for this project.' . $crlf
                                          . ' *' . $crlf
                                          . ' * -------------------------------------------------------------------------' . $crlf
                                          . ' */' );

            helperCreateFile ( 'script.js', '/**' . $crlf
                                          . ' * -------------------------------------------------------------------------' . $crlf
                                          . ' *' . $crlf
                                          . ' * Browser scripts for this project.' . $crlf
                                          . ' *' . $crlf
                                          . ' * -------------------------------------------------------------------------' . $crlf
                                          . ' */' );

            helperCreateFile ( 'favicon.svg', '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368">' . $crlf
                                            . '    <path d="M280-400q17' . $crlf
                                            . '             0' . $crlf
                                            . '             28.5-11.5T320-440q0-17-11.5-28.5T280-480q-17' . $crlf
                                            . '             0-28.5' . $crlf
                                            . '             11.5T240-440q0 17 11.5' . $crlf
                                            . '             28.5T280-400Zm0-120q17' . $crlf
                                            . '             0' . $crlf
                                            . '             28.5-11.5T320-560q0-17-11.5-28.5T280-600q-17' . $crlf
                                            . '             0-28.5 11.5T240-560q0 17 11.5 28.5T280-520Zm0-120q17' . $crlf
                                            . '             0' . $crlf
                                            . '             28.5-11.5T320-680q0-17-11.5-28.5T280-720q-17' . $crlf
                                            . '             0-28.5 11.5T240-680q0 17 11.5' . $crlf
                                            . '             28.5T280-640Zm120 240h200v-80H400v80Zm0-120h320v-80H400v80Zm0-120h320v-80H400v80ZM80-80v-720q0-33' . $crlf
                                            . '             23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0' . $crlf
                                            . '             33-23.5' . $crlf
                                            . '             56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46' . $crlf
                                            . '             0v-480' . $crlf
                                            . '             480Z"/>' . $crlf
                                            . '</svg>' );
        }
    };

    /**
     * ---------------------------------------------------------------------
     *
     * Creates a new file in the current project.
     *
     * ---------------------------------------------------------------------
     *
     * First, we use the "helperHasProject" routine to check that the
     * project data contains an element "dir" with a non-empty directory
     * name.
     *
     * Then we append an entry about the requested file name inside an
     * element "files". TRUE means that this file is editable. And save the
     * result back in the session under the "userProject" parameter.
     *
     * Next, we create a directory for that file, and then create the file
     * itself, storing the proposed content into it.
     *
     * ---------------------------------------------------------------------
     */

    function helperCreateFile ( $filename, $content = '' ) {
        if ( $data = helperHasProject ( ) ) {

            $data[ 'files' ][ $filename ] = TRUE;
            global $app;
                   $app->session->set ( 'userProject', $data );

            $filename = helperGetStorage ( ) . $data[ 'dir' ] . '/' . $filename;
            $filename = mimimiBasePath ( $filename, TRUE );
            @ mkdir ( dirname ( $filename ), 0755, TRUE );

            @ file_put_contents ( $filename, $content );
        }
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Uploads a new file in the current project.
     *
     * ---------------------------------------------------------------------
     *
     * First, we use the "helperHasProject" routine to check that the
     * project data contains an element "dir" with a non-empty directory
     * name.
     *
     * Then we append an entry about the requested file name inside an
     * element "files". FALSE means that this file is not editable (viewable
     * only). And save the result back in the session under the "userProject"
     * parameter.
     *
     * Next, we create a directory for that file, and then copy the uploaded
     * content into it.
     *
     * ---------------------------------------------------------------------
     */

    function helperUploadFile ( $filename, $uploaded ) {
        if ( $data = helperHasProject ( ) ) {

            $data[ 'files' ][ $filename ] = FALSE;
            global $app;
                   $app->session->set ( 'userProject', $data );

            $filename = helperGetStorage ( ) . $data[ 'dir' ] . '/' . $filename;
            $filename = mimimiBasePath ( $filename, TRUE );
            @ mkdir ( dirname ( $filename ), 0755, TRUE );

            @ copy ( $uploaded, $filename );
        }
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Closes the current project.
     *
     * ---------------------------------------------------------------------
     */

    function helperCloseProject ( ) {
        global $app;
               $app->session->set ( 'userProject', [ ] );
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Returns a RegExp pattern to validate the project file names.
     *
     * ---------------------------------------------------------------------
     */

    function helperFilePattern ( $plusViewer = FALSE ) {
        $symbol    = '[0-9a-zA-Z_-]';
        $symbols   = '[0-9a-zA-Z._-]*?';
        $directory = '(' . $symbols . $symbol . '/)*';
        $filename  = $symbols . $symbol . '\.(' . helperFileExtensions ( $plusViewer ) . ')';
        return       '~^' . $directory . $filename . '$~u';
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Returns a part of RegExp pattern to validate the project file extensions.
     *
     * ---------------------------------------------------------------------
     */

    function helperFileExtensions ( $plusViewer = FALSE ) {
        global $app;
        return $app->projects->editorExtensions . ( $plusViewer
                                                         ? '|' . $app->projects->viewerExtensions
                                                         : ''                                     );
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Displays a menu.
     *
     * ---------------------------------------------------------------------
     */

    function helperDisplayMenu ( $map, $asToolbar = FALSE ) {
        if ( $map ) {
            $iconkey = $asToolbar ? 'toolicon'
                                  : 'icon';
            echo '<ul class="items">';
                     foreach ( $map as $name => $entry ) {
                         echo '<li class="item"' . ( is_string ( $name ) ? ' data-name="' . printValue ( $name, FALSE ) . '">' : '>' );
                                  if ( ! $asToolbar || ! empty ( $entry[ $iconkey ] ) ) {
                                      echo '<button class="link"' . ( ! empty   ( $entry[ 'inactive' ] ) ? ' disabled'                                                                                        : ''  )
                                                                  . ( ! empty   ( $entry[ 'url'      ] ) ? ' data-href="'  . printValue ( preg_replace ( '~/+$~u', '', $entry[ 'url'      ] ), FALSE ) . '"'  : ''  )
                                                                  . ( isset     ( $entry[ 'shortcut' ] ) ? ' data-key="'   . printValue (                              $entry[ 'shortcut' ]  , FALSE ) . '"'  : ''  )
                                                                  . ( ! empty   ( $entry[ 'title'    ] ) ? ' data-title="' . printValue (                              $entry[ 'title'    ]  , FALSE ) . '">' : '>' )
                                                                  . ( ! empty   ( $entry[ $iconkey   ] ) ? ( preg_match ( '~^\s*<svg\s~ui', $entry[ $iconkey ] )
                                                                                                                                          ? $entry[ $iconkey ]
                                                                                                                                          : '<img class="icon" src="' . printValue ( $entry[ $iconkey ], FALSE ) . '" alt="" loading="lazy" decoding="async">' ) : '' )
                                                                  . ( isset     ( $entry[ 'name'     ] ) ? '<span class="name">' . printValue ( $entry[ 'name'     ], FALSE ) . '</span>' : ''  )
                                         . '</button>';
                                  }
                                  if ( ! empty ( $entry[ 'subitems' ] ) ) helperDisplayMenu ( $entry[ 'subitems' ], $asToolbar );
                         echo '</li>';
                     }
            echo '</ul>';
        }
    };

    /**
     * ---------------------------------------------------------------------
     *
     * Authorizes an user by its login and password.
     *
     * ---------------------------------------------------------------------
     */

    function helperLoginUser ( $login, $pass ) {
        global $app;
        $id  = $app->users->getId ( $login, $pass );
        $id  ? $app->session->set ( 'userInfo', [ 'id' => $id ] )
             : helperLogoutUser ( );
        return ! empty ( $id );
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Cancels the current user's authorization.
     *
     * ---------------------------------------------------------------------
     */

    function helperLogoutUser ( ) {
        global $app;
               $app->session->set ( 'userInfo',    [ ] );
        helperCloseProject ( );
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Checks if the site is currently working in production mode.
     *
     * ---------------------------------------------------------------------
     */

    function helperHasProduction ( ) {
        global $app;
        return ! empty ( $app->onProduction );
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Checks if the current user is an administrator.
     *
     * ---------------------------------------------------------------------
     */

    function helperHasAdmin ( ) {
        global $app;
        return $app->checkForAdmin ( );
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Checks if the project is already open.
     *
     * ---------------------------------------------------------------------
     *
     * Here we check that the project data contains an element "dir" with
     * a non-empty directory name. In this case, we return the data obtained
     * during the check. Otherwise, FALSE.
     *
     * ---------------------------------------------------------------------
     */

    function helperHasProject ( ) {
        global  $app;
        $data = $app->projects->getProject ( );
        return mimimiSafePath ( $data[ 'dir' ] )
                              ? $data
                              : FALSE;
    }


    /**
     * ---------------------------------------------------------------------
     *
     * Checks if the project files have been modified.
     *
     * ---------------------------------------------------------------------
     */

    function helperHasChanges ( ) {
        return           $data = helperHasProject ( )
            && ! empty ( $data[ 'modified' ] );
    }

    /**
     * ---------------------------------------------------------------------
     *
     * Checks if the project already contains such a file.
     *
     * ---------------------------------------------------------------------
     *
     * First, we use the "helperHasProject" routine to check that the
     * project data contains an element "dir" with a non-empty directory
     * name.
     *
     * Next, we scan all the "files" elements in the obtained data to find
     * one whose lowercase name matches the requested file name.
     *
     * If the search is successful, we return that name. Otherwise, FALSE.
     *
     * ---------------------------------------------------------------------
     */

    function helperHasFile ( $filename ) {
        if ( $data = helperHasProject ( ) ) {

            $filename = mb_strtolower ( $filename, 'UTF-8' );
            foreach ( $data[ 'files' ] as $name => $editable ) {

                $found = $filename == mb_strtolower ( $name, 'UTF-8' );
                if ( $found ) return $name;
            }
        }
        return FALSE;
    }
