<?php
/**
 * -------------------------------------------------------------------------
 *
 * The basis for a module that works with the "database" entities.
 *
 * -------------------------------------------------------------------------
 *
 * It implements several of basic methods:
 *
 *     PUBLIC     run                  ( [$url]                        )  -->  to return an entity content by its URL
 *     PUBLIC     remove               ( $url                          )  -->  to remove an entity by its URL
 *     PUBLIC     save                 ( $url, $data                   )  -->  to save an entity to its URL
 *     PUBLIC     upload               ( $url, $data                   )  -->  to upload an entity to its URL
 *     PUBLIC     download             ( $url                          )  -->  to download an entity by its URL
 *     PUBLIC     getMap               ( [$withNoExtension]            )  -->  to retrieve a sitemap (that is, a list of relative entity URLs)
 *     PUBLIC     getStorageExtension  (                               )  -->  to return extension used for every entity file in "database"
 *     PUBLIC     getStoragePath       (                               )  -->  to return the "database" path to store entities as some files
 *     PROTECTED  asFilename           ( $url                          )  -->  to convert an entity URL to its relative filename
 *     PROTECTED  asAbsolute           ( $url[, $level]                )  -->  to convert an entity URL to its absolute filename
 *     PROTECTED  listFiles            ( $extension, $path[, $subpath] )  -->  to get a list of specific files in a directory
 *
 * The following files were used below:
 *
 *     mimimi.core/NodeModule.php  -->  to import the basic module class for extending it into MimimiDatabaseModule class
 *
 * -------------------------------------------------------------------------
 *
 * @package    MimimiFramework
 * @subpackage Examples / Static Pages only
 * @license    GPL-2.0
 *             https://opensource.org/license/gpl-2-0/
 * @copyright  2022 MiMiMi Community
 *             https://mimimi.software/
 *
 * -------------------------------------------------------------------------
 */

    mimimiInclude ( 'Module.php' );

    class MimimiDatabaseModule extends MimimiModule {

        /**
         * -----------------------------------------------------------------
         *
         * Returns an entity content by its URL.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $url  (optional) Requested entity URL.
         * @return  string
         *
         * -----------------------------------------------------------------
         */

        public function run ( $url = '' ) {
            $file = $this->asAbsolute ( $url );
            return file_exists ( $file )
                && is_file     ( $file )
                && is_readable ( $file ) ? @ file_get_contents ( $file )
                                         :   '';
        }

        /**
         * -----------------------------------------------------------------
         *
         * Removes an entity by its URL.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $url  Requested entity URL.
         * @return  void
         *
         * -----------------------------------------------------------------
         */

        public function remove ( $url ) {
            $level = 0;
            $file = $this->asAbsolute ( $url, $level );
            if ( file_exists ( $file )
            &&   is_file     ( $file )
            &&   is_readable ( $file ) ) {
                @ unlink ( $file );
                while ( $level > 1 ) {
                    $level--;
                    $file = dirname ( $file );
                    if ( ! @ rmdir ( $file ) ) break;
                }
            }
        }

        /**
         * -----------------------------------------------------------------
         *
         * Saves an entity to its URL.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $url   Requested entity URL.
         * @param   string  $data  A file content to save.
         * @return  void
         *
         * -----------------------------------------------------------------
         */

        public function save ( $url, $data ) {
            $level = 0;
            $file = $this->asAbsolute ( $url, $level );
            if ( $level > 1 ) {
                $dir = dirname ( $file );
                @ mkdir ( $dir, 0755, TRUE );
            }
            @ file_put_contents ( $file, $data );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Uploads an entity to its URL.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $url   Requested entity URL.
         * @param   array   $data  A file structure provided by the server during upload.
         * @return  void
         *
         * -----------------------------------------------------------------
         */

        public function upload ( $url, $data ) {
            $this->remove ( $url );
            $level = 0;
            $file = $this->asAbsolute ( $url, $level );
            if ( $level > 1 ) {
                $dir = dirname ( $file );
                @ mkdir ( $dir, 0755, TRUE );
            }
            @ move_uploaded_file ( $data[ 'tmp_name' ], $file );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Downloads an entity by its URL.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $url  Requested entity URL.
         * @return  void
         *
         * -----------------------------------------------------------------
         */

        public function download ( $url ) {
            $file = $this->asAbsolute ( $url );
            if ( file_exists ( $file )
            &&   is_file     ( $file )
            &&   is_readable ( $file ) ) {
                $type = mime_content_type ( $file );
                $type = empty ( $type ) ? 'application/octet-stream'
                                        : $type;
                $name = $this->asFilename ( $url );
                @ header ( 'Content-Description: File Transfer',                        TRUE );
                @ header ( 'Content-Type: ' . $type,                                    TRUE );
                @ header ( 'Content-Disposition: attachment; filename="' . $name . '"', TRUE );
                @ header ( 'Content-Transfer-Encoding: binary',                         TRUE );
                @ header ( 'Expires: 0',                                                TRUE );
                @ header ( 'Cache-Control: must-revalidate',                            TRUE );
                @ header ( 'Pragma: public',                                            TRUE );
                @ header ( 'Content-Length: ' . filesize ( $file ),                     TRUE );
                @ readfile ( $file );
                exit;
            }
        }

        /**
         * -----------------------------------------------------------------
         *
         * Retrieves the list of entity URLs.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   bool        $withNoExtension  (optional) TRUE if you want to remove a file extension from URLs.
         * @return  array|bool                    List of relative URLs on success.
         *                                        FALSE on failure. This means no URLs were found.
         *
         * -----------------------------------------------------------------
         */

        public function getMap ( $withNoExtension = TRUE ) {
            $extension = $this->getStorageExtension ( );
            $path      = $this->getStoragePath      ( );
            $urls      = $this->listFiles ( $extension, $path );
            if ( $withNoExtension ) {
                $pattern = '~\.' . preg_quote ( $extension, '~' ) . '$~ui';
                foreach ( $urls as & $url ) {
                    $url = preg_replace ( $pattern, '', $url );
                }
            }
            return $urls;
        }

        /**
         * -----------------------------------------------------------------
         *
         * Returns the extension used for every entity file in "database".
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @return  string
         *
         * -----------------------------------------------------------------
         */

        public function getStorageExtension ( ) {
            return '';
        }

        /**
         * -----------------------------------------------------------------
         *
         * Returns the "database" path to store entities as some files.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @return  string
         *
         * -----------------------------------------------------------------
         */

        public function getStoragePath ( ) {
            return '';
        }

        /**
         * -----------------------------------------------------------------
         *
         * Converts an entity URL to its relative filename.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $url  Requested entity URL.
         * @return  string
         *
         * -----------------------------------------------------------------
         */

        public function asFilename ( $url ) {
            $extension = $this->getStorageExtension ( );
            $pattern   = '~\.' . preg_quote ( $extension, '~' ) . '$~ui';
            $url = preg_replace ( '~^[^\w_]~u',   '-',  $url );
            $url = preg_replace ( '~/+[^\w_/]~u', '/-', $url );
            $url = preg_replace ( '~[^\w_]$~u',   '-',  $url );
            $url = preg_replace ( '~[^\w_/]/+~u', '-/', $url );
            $url = preg_replace ( '~[^\w_/.]~u',  '-',  $url );
            return preg_replace ( $pattern,       '',   $url ) . '.' . $extension;
        }

        /**
         * -----------------------------------------------------------------
         *
         * Converts an entity URL to its absolute filename.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $url    Requested entity URL.
         * @param   int     $level  (optional) An external variable that will receive the nesting level of that file.
         * @return  string
         *
         * -----------------------------------------------------------------
         */

        public function asAbsolute ( $url, & $level = 0 ) {
            $path  = $this->getStoragePath ( );
            $file  = $this->asFilename ( $url );
            $level = count (
                         preg_split ( '~/~u', $file )
                     );
            return mimimiBasePath ( $path . $file, TRUE );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Lists files with the specified extension contained in a directory.
         *
         * -----------------------------------------------------------------
         *
         * @protected
         * @param      string  $extension  A file extension you want to search for.
         * @param      string  $path       Relative path to the directory to be scanned. It must end with a slash.
         * @param      string  $subpath    (optional) Relative subpath to start scanning from. If specified, it must also end with a slash.
         * @return     array               List of file names relative to that directory.
         *
         * ---------------------------------------------------------------------
         */

        protected function listFiles ( $extension, $path, $subpath = '' ) {
            $list = [ ];
            $pattern = '~\.' . preg_quote ( $extension, '~' ) . '$~ui';
            $names = mimimiFolders ( $path . $subpath, [ ], TRUE );
            foreach ( $names as $name ) {
                if ( preg_match ( $pattern, $name ) ) {
                    $list[ ] = $subpath . $name;
                }
            }
            $names = mimimiFolders ( $path . $subpath );
            foreach ( $names as $name ) {
                $sublist = $this->listFiles ( $extension, $path, $subpath . $name . '/' );
                if ( $sublist ) {
                    $list = array_merge ( $list, $sublist );
                }
            }
            return $list;
        }
    };
