<?php
/**
 * -------------------------------------------------------------------------
 *
 * The module for collecting any viewers.
 *
 * -------------------------------------------------------------------------
 *
 * This module is a simple node module named VIEWERS according to its
 * meaning. Hovewer, it only collects viewers, and its real viewers are
 * nested modules (submodules). They are designed to read specific database
 * records located at URLs associated with their particular submodule. For
 * example, the following URLs are associated with a submodule named NEWS:
 *
 *     https://your.site/news
 *     https://your.site/news/page-2
 *     https://your.site/news/page-3
 *     https://your.site/news/page-NNN            <─ where NNN is a number
 *     https://your.site/news/sitemap
 *     https://your.site/news/hello-world
 *     https://your.site/news/hello-world/delete  <─ available only to admin
 *     https://your.site/news/hello-world/edit    <─ available only to admin
 *     https://your.site/news/add                 <─ available only to admin
 *
 * Each submodule is a named folder and a nested PHP file of the same name
 * with a certain class and methods declared there. And any submodule must
 * have at least these public methods:
 *
 *     run        ( urlToParse                                         )  ─> template filename or NOT STRING
 *     getItem    ( urlToSearch,                            [forAdmin] )  ─> database record or FALSE
 *     getItems   (                pageNumber,  [pageSize], [forAdmin] )  ─> list of records or FALSE
 *     searchBy   ( queryToSearch, pageNumber,  [pageSize], [forAdmin] )  ─> list of records or FALSE
 *     getRecent  (                             [pageSize], [forAdmin] )  ─> list of latest records or FALSE
 *     getSitemap (                             [mapSize]              )  ─> list of URLs or FALSE
 *
 * The submodule class formed by concatenating the class name of its owning
 * node module and the folder name of the submodule. For example, the
 * NEWS submodule must have a class like this:
 *
 *     MyMimimiViewers + News  ─> MyMimimiViewersNews
 *
 * If you want to extend the functionality of your website with other
 * viewers, just add their submodules to the VIEWERS module folder, and
 * the corresponding templates to the website's theme folder. For example,
 * for a TEST submodule, you would need the following templates:
 *
 *     tiny.news.feed/Themes/default/test-add.tpl
 *     tiny.news.feed/Themes/default/test-delete.tpl
 *     tiny.news.feed/Themes/default/test-edit.tpl
 *     tiny.news.feed/Themes/default/test-list.tpl
 *     tiny.news.feed/Themes/default/test-page.tpl
 *     tiny.news.feed/Themes/default/test-sitemap.tpl
 *
 * Any of these templates are optional. If the theme of your website does
 * not have a specific template, the function associated with it will
 * automatically be unavailable.
 *
 * -------------------------------------------------------------------------
 *
 * How did this file run?
 *
 *     ├─> .htaccess
 *     └─> index.php
 *           │   │
 *           │   └─<─ constant MIMIMI_APP_FOLDER
 *           │
 *           ├─> tiny.news.feed/Config.php
 *           ├─> mimimi.core/Routines.php
 *           │               │
 *           │               ├─<─ routine mimimiInclude()
 *           │               ├─<─ routine mimimiFolders()
 *           │               ├─<─ routine mimimiPost()
 *           │               └─<─ routine mimimiGet()
 *           │
 *           ├─> [MAKE THE $app GLOBAL VARIABLE]
 *           │
 *           ├─> tiny.news.feed/Constants.php
 *           │                                      ┌─<─ class mimimi.core/Module.php
 *           │                                      │                        └─> __construct()
 *           │               ┌─<─ class mimimi.core/NodeModule.php
 *           │               │                           └─> __construct()
 *           └─> tiny.news.feed/Application.php
 *                              │    └─> run()                ┌─<─ class mimimi.core/Module.php
 *                              │         │                   │                        └─> __construct()
 *                              │         ├─> mimimi.core/Has/Has.php
 *                              │         │                    └─> __get()
 *                              │         │
 *                              │         │                         ┌─<─ class mimimi.core/Module.php
 *                              │         │                         │                        └─> __construct()
 *                              │         └─> tiny.news.feed/Router/Router.php
 *                              │                                   │  └─> run()
 *                              ├─<─ method getLatest()             │       │
 *                              ├─<─ method getViewers()            │       ├─> [MAKE THE $item GLOBAL VARIABLE  ]
 *                              ├─<─ method getSitemap()            │       ├─> [MAKE THE $items GLOBAL VARIABLE ]
 *                              ├─<─ method getList()               │       ├─> [MAKE THE $page GLOBAL VARIABLE  ]
 *                              ├─<─ method getPage()               │       ├─> [MAKE THE $viewer GLOBAL VARIABLE]
 *                              └─<─ method amIAdmin()              │       ├─> [MAKE THE $url GLOBAL VARIABLE   ]
 *                                                                  │       │
 *                                                                  │       │                              ┌─<─ class mimimi.core/Module.php
 *                                                                  │       ├─> cutViewer()                │                        └─> __construct()
 *                                                                  │       │       └─> mimimi.modules/Url/Url.php
 *                                                                  │       │                               ├─> cutSegment()
 *                                                                  │       │                               └─> cutPaginator()        ┌─<─ class mimimi.core/Module.php
 *                                                                  │       │                                                         │                        └─> __construct()
 *                                                                  │       │                                  ┌─<─ class mimimi.core/NodeModule.php
 *                                                                  │       ├─> checkFor()                     │                           └─> __construct()
 *                                                                  │       │       └─> tiny.news.feed/Viewers/Viewers.php
 *                                                                  │       │                                  │  │
 *                                                                  │       │                                  │  └─> [LAUNCH A VIEWER MODULE]
 *                                                                  │       │                                  │
 *                                                                  │       └─> [RENDER A THEME TEMPLATE]      ├─<─ method getViewerNames()
 *                                                                  │                                          ├─<─ method filterNames()
 *                                                                  ├─<─ method checkFor()                     ├─<─ method cropList()
 *                                                                  ├─<─ method findMe()                       ├─<─ method getQueryParam()
 *                                                                  ├─<─ method cutViewer()                    └─<─ method parseQuery()
 *                                                                  ├─<─ method cutPagenum()
 *                                                                  └─<─ method cutCommand()
 *
 * The down-right arrows show the order in which app files are loaded and
 * their methods that are called when processing a request to the site.
 * The left-down arrows show the classes from which the corresponding
 * application file is derived. The left-up arrows show some of the public
 * routines or some of the public methods that the corresponding file
 * exposes to other application modules.
 *
 * -------------------------------------------------------------------------
 *
 * @package    MimimiFramework
 * @subpackage Examples / Tiny News Feed site
 * @license    GPL-2.0
 *             https://opensource.org/license/gpl-2-0/
 * @copyright  2022 MiMiMi Community
 *             https://mimimi.software/
 *
 * -------------------------------------------------------------------------
 */

                                                        // -----------------
mimimiInclude(                                          // load a file              ( see file mimimi.core/Routines.php   -> mimimiInclude )
    'NodeModule.php'                                    // . . with this base class ( see file mimimi.core/NodeModule.php -> MimimiNodeModule )
);                                                      //
                                                        // -----------------
class MyMimimiViewers extends MimimiNodeModule {        // define required "My..." class for this module named VIEWERS

    /**
     * ---------------------------------------------------------------------
     *
     * Retrieves the names of installed viewers.
     *
     * ---------------------------------------------------------------------
     *
     * They are the lowercase names of all nested folders containing
     * submodules of the current node module named VIEWERS.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @return  array  The list of viewer names.
     *
     * ---------------------------------------------------------------------
     */

    public function getViewerNames () {                 // -----------------
        $result = [];                                   // there is no result
                                                        // -----------------
        $names = mimimiFolders(                         // get a list of subfolders      ( see file mimimi.core/Routines.php -> mimimiFolders )
                     MIMIMI_APP_FOLDER . 'Viewers'      // . . located in this directory ( see file index.php                -> MIMIMI_APP_FOLDER )
                 );                                     //
                                                        // -----------------
        foreach ( $names as $name ) {                   // walk through the provided subfolder names
            $name = strtolower($name);                  //     viewer name must be in lower case
                                                        // -----------------
            $ok = $this                                 //     check if this node module has that submodule installed
                  ->has                                 //     . . via HAS module           ( see file mimimi.core/Has/Has.php -> __get )
                  ->$name;                              //     . . check for this submodule ( see file tiny.news.feed/Viewers/[$name]/[$name].php )
            if ( $ok ) {                                //     if it's true
                $result[] = $name;                      //         add this name to the list
            }                                           //
        }                                               //
                                                        // -----------------
        return $result;                                 // return a list of names
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Drops viewer names that are not relevant.
     *
     * ---------------------------------------------------------------------
     *
     * The submodule name is considered irrelevant if it does not have a
     * corresponding template file in the website theme. The input parameter
     * $pattern is intended to provide a specimen to search for this file,
     * where the * character is the name of the viewer under test. For
     * example, if you want to drop viewer names that do not match sitemap
     * functionality, you can call the method like this:
     *
     *     $viewers = [ 'get',
     *                  'some',
     *                  'viewer',
     *                  'names' ];
     *     $list = $this->app->viewers->filterNames( $viewers, '*-sitemap.tpl' );
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   array   $names    The list of names to be filtered.
     * @param   string  $pattern  The pattern of template file to find irrelevant viewers.
     * @return  array             The filtered list.
     *
     * ---------------------------------------------------------------------
     */

    public function filterNames ( $names, $pattern ) {  // -----------------
        $ok = $this                                     // check if themes are installed in this app
              ->app                                     // . . via APPLICATION module ( see file tiny.news.feed/Application.php )
              ->has                                     // . . via HAS module         ( see file mimimi.core/Has/Has.php -> __get )
              ->themes;                                 // . . check for this module  ( see file tiny.news.feed/Themes/Themes.php )
        if ( $ok ) {                                    // if it's true
            foreach ( $names as $idx => $name ) {       //     walk through the provided viewer names
                $file = str_replace(                    //         build the template file
                            '*',                        //         . .
                            $name,                      //         . . with this viewer name
                            $pattern                    //         . . using this specimen
                        );                              //
                                                        // -----------------
                $ok = $this                             //         find it
                      ->app                             //         . . via APPLICATION module ( see file tiny.news.feed/Application.php )
                      ->themes                          //         . . via THEMES module      ( see file tiny.news.feed/Themes/Themes.php -> checkFor )
                      ->checkFor($file);                //         . . using this method
                if ( ! $ok ) {                          //         if the theme does not have that template
                    unset($names[$idx]);                //             remove this viewer name from list
                }                                       //
            }                                           //
        }                                               //
                                                        // -----------------
        return $names;                                  // return the filtered list
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Crops the right edge of a list to fit the page.
     *
     * ---------------------------------------------------------------------
     *
     * There are different ways to determine the existence of a next subpage
     * when a website page contains a long list of publications divided into
     * subpages.
     *
     * This application implements a simple way that retrieves a list of
     * publications equal to the size of the currently viewed subpage plus
     * one extra publication. The retrieval of publications is performed by
     * a specific viewer submodule using its getItems() method.
     *
     * The resulting list is then cropped to the correct size, and the record
     * of extra publication is replaced with the value FALSE, which means a
     * NextPage virtual marker. That marker is a signal to the website
     * template to add the NexPage button at this location.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   array  $list  An array of records fetched from the database.
     * @param   int    $size  Maximum page capacity.
     * @return  array         The cropped incoming array.
     *
     * ---------------------------------------------------------------------
     */

    public function cropList ( $list, $size ) {         // -----------------
        if ( $list ) {                                  // must be a non-empty list
            $count = count($list);                      //     measure the list
            if ( $count > $size ) {                     //     if it is more than a page
                $list = array_slice($list, 0, $size);   //         cut off extra elements
                $list[] = FALSE;                        //         add the NextPage marker
            }                                           //
        }                                               //
        return $list;                                   // return the cropped list
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Retrieves the POST or GET parameter from the page request.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   string  $param    The name of the parameter to be retrieved.
     * @param   mixed   $default  (optional) Default value if the page request does not have such a parameter.
     * @return  string
     *
     * ---------------------------------------------------------------------
     */

    public function getQueryParam ( $param, $default = '' ) {
                                                        // -----------------
        return preg_replace(                            // return the page request value
                   '~(^\s+|\s+$)~u',                    // . . with trailing spaces removed
                   '',                                  // . .
                   mimimiPost(                          // . . from this POST parameter ( see file mimimi.core/Routines.php -> mimimiPost )
                       $param,                          //     . .
                       mimimiGet(                       //     . . otherwise from this GET parameter ( see file mimimi.core/Routines.php -> mimimiGet )
                           $param,                      //         . .
                           $default                     //         . . otherwise from this default value
                       )                                //
                   )                                    //
               );                                       //
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Parses the query into words.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   string  $query  The query string to search for.
     * @param   int     $max    (optional) Maximum number of words to be parsed. Default is 4 words.
     * @return  array           List of parsed words.
     *
     * ---------------------------------------------------------------------
     */

    public function parseQuery ( $query, $max = 4 ) {   // -----------------
        $result = [];                                   // no words found
        if ( is_string($query) ) {                      // if there is a query
            $chars = '[\s\xA0\W_%?"\'`/\\\\]+';         //     define unused symbols (to remove not alphanumerics)
                                                        // -----------------
            $query = preg_replace(                      //     remove unused symbols
                         '~^' . $chars . '~u',          //     . . before query
                         '',                            //     . .
                         $query                         //     . .
                     );                                 //
                                                        // -----------------
            $query = preg_replace(                      //     remove unused symbols
                         '~' . $chars . '$~u',          //     . . after query
                         '',                            //     . .
                         $query                         //     . .
                     );                                 //
                                                        // -----------------
            if ( $query ) {                             //     if that query is still valid
                $query = preg_split(                    //         break the query into words
                             '~' . $chars . '~u',       //         . .
                             $query                     //         . .
                         );                             //
                                                        // -----------------
                $result = array_slice($query, 0, $max); //         no more than so many words
            }                                           //
        }                                               //
                                                        // -----------------
        return $result;                                 // return a list of words
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Full filename of this script file.
     *
     * ---------------------------------------------------------------------
     *
     * See the "mimimi.core/NodeModule.php" file to understand this
     * technical property. According to the framework standard, this property
     * must be reinitialized in the same way in any new node type module.
     * Therefore, the same line is repeated here as in the file just mentioned.
     *
     * ---------------------------------------------------------------------
     *
     * @access protected
     * @var    string
     *
     * ---------------------------------------------------------------------
     */

                                                        // -----------------
    protected $myNodeFile = __FILE__;                   // remember the physical location of this VIEWERS module in the file structure
                                                        // -----------------
};