<?php
/**
 * -------------------------------------------------------------------------
 *
 * The module for working with database records stored in the "news" table.
 *
 * -------------------------------------------------------------------------
 *
 * How did this file run?
 *
 *     ├─> .htaccess
 *     └─> index.php
 *           ├─> tiny.news.feed/Config.php
 *           ├─> mimimi.core/Routines.php
 *           │               │
 *           │               ├─<─ routine mimimiInclude()
 *           │               └─<─ routine mimimiRandomId()
 *           │
 *           ├─> [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()             ┌─<─ class mimimi.core/Module.php
 *                                                                  │       │       └─> tiny.news.feed/Viewers/Viewers.php                                               │                        └─> __construct()
 *                                                                  │       │                                  │  │                               ┌─<─ class mimimi.core/ModuleWithTable.php
 *                                                                  │       │                                  │  │                               │                      │
 *                                                                  │       │                                  │  └─> tiny.news.feed/Viewers/News/News.php               ├─<─ method create()
 *                                                                  │       │                                  │                                  │ │                    ├─<─ method add()
 *                                                                  │       │                                  ├─<─ method getViewerNames()       │ │  ┌─ run()       ─┐ ├─<─ method update()
 *                                                                  │       │                                  ├─<─ method filterNames()          │ │  ├─ getItem()   ─┤ ├─<─ method remove()
 *                                                                  │       └─> [RENDER A THEME TEMPLATE]      ├─<─ method cropList()             │ └─>├─ getItems()  ─┤ ├─<─ method get()
 *                                                                  │                                          ├─<─ method getQueryParam()        │    ├─ getRecent() ─┤ └─<─ method select()
 *                                                                  ├─<─ method checkFor()                     └─<─ method parseQuery()           │    └─ getSitemap() ┘
 *                                                                  ├─<─ method findMe()                                                          │
 *                                                                  ├─<─ method cutViewer()                                                       ├─<─ method searchBy()
 *                                                                  ├─<─ method cutPagenum()                                                      └─<─ method testBy()
 *                                                                  └─<─ 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 )
    'ModuleWithTable.php'                               // . . with this base class ( see file mimimi.core/ModuleWithTable.php -> MimimiModuleWithTable )
);                                                      //
                                                        //
class MyMimimiViewersNews extends MimimiModuleWithTable { // define required "My..." class for this module named NEWS

    /**
     * ---------------------------------------------------------------------
     *
     * Define a table name, structure, and keys.
     *
     * ---------------------------------------------------------------------
     *
     * Important! We're using the TIMESTAMP type instead of DATETIME, so
     *            setting the default value will work in older versions
     *            of MariaDB that don't support the CURRENT_TIMESTAMP
     *            primitive on DATETIME columns.
     *
     * ---------------------------------------------------------------------
     */

                                                        // -----------------
    protected $table = 'news';                          // table name for this module
    protected $tableFields = [                          // columns of the table
                                                        //
                  '`id`       BIGINT(20)     NOT NULL  AUTO_INCREMENT             COMMENT "record identifier"',
                  '`url`      VARCHAR(250)   NOT NULL                             COMMENT "page relative URL"',
                  '`title`    VARCHAR(100)   NOT NULL                             COMMENT "news name"',
                  '`meta`     VARCHAR(300)   NOT NULL                             COMMENT "short info"',
                  '`image`    VARCHAR(250)   NOT NULL                             COMMENT "URL of photo"',
                  '`body`     VARCHAR(6000)  NOT NULL                             COMMENT "HTML text"',
                  '`enabled`  BOOLEAN        NOT NULL  DEFAULT FALSE              COMMENT "1 if allow display on the site"',
                  '`created`  TIMESTAMP      NOT NULL  DEFAULT CURRENT_TIMESTAMP  COMMENT "creation date"'
              ];                                        //
                                                        // -----------------
    protected $tableKeys = [                            // keys of the table
                                                        //
                  'PRIMARY KEY (`id`)',                 //
                  'UNIQUE  KEY (`url`)',                //
                  'KEY         (`enabled`)',            //
                  'KEY         (`created`)'             //
              ];                                        //
                                                        // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Define demo records if you need.
     *
     * ---------------------------------------------------------------------
     */

    protected $demoRows = [
        [
            'url'      => 'demo-posts',
            'title'    => 'Everything is fine',
            'meta'     => 'This is the first time you have installed this web application. Let\'s generate a feed below from a few demo posts. This way you can see what the website looks like when it\'s filled with content. At the same time, you will be able to read below a little information about this web app\'s capabilities.',
            'image'    => 'media/demo-posts/tiny-news-feed/welcome.jpg',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 20:31:42'
        ], [
            'url'      => 'demo-posts/with-short-text-only',
            'meta'     => 'The first thing to note is that you can write a post using the Short Text field only. That post will appear in the News List without a Read More button. Just like this example you are reading now. And although the post still has its own page it is not indexed and will not be visible in the site map.',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 20:20:31'
        ], [
            'url'      => 'demo-posts/about-administrative-buttons',
            'meta'     => 'By the way, if you are an admin, you may touch/tap/click a post to see its administrative buttons',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 20:09:20'
        ], [
            'url'      => 'demo-posts/with-image-and-short-text-only',
            'meta'     => 'The second thing to note is that you can write a post using the Short Text and the Image fields only. And a long tap/click on this image will expand it to full screen.',
            'image'    => 'media/demo-posts/tiny-news-feed/image-1.jpg',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 19:58:09'
        ], [
            'url'      => 'demo-posts/with-title-only',
            'title'    => 'You may write one line message',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 19:46:58'
        ], [
            'url'      => 'demo-posts/with-image-and-title-only',
            'title'    => 'You may write one line with image',
            'image'    => 'media/demo-posts/tiny-news-feed/image-2.jpg',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 19:35:47'
        ], [
            'url'      => 'demo-posts/with-title-and-short-text-only',
            'title'    => 'You can type a title...',
            'meta'     => '... and then use the Short Text field to publish a short post with a name, text and no image. Please note that the Title field is 100 characters long and the Short Text field is 300 characters long.',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 19:24:36'
        ], [
            'url'      => 'demo-posts/with-image-only',
            'image'    => 'media/demo-posts/tiny-news-feed/image-3.jpg',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 19:13:25'
        ], [
            'url'      => 'demo-posts/with-title-and-short-text-and-full-text',
            'title'    => 'And finally',
            'meta'     => 'You can write a long post with a name, short text, an image (or no image, as you wish). Then use the Full Text field, which allows you to use HTML markup. That post will appear in the News List with a Read More button. Just like this example. Note that the Full Text field is 6000 characters long.',
            'body'     => '<h5>It is a Full Text example</h5>' .
                          '<p>Films are works of cinematographic art created with the help of cinema equipment. Cinema occupies a significant part of human culture.</p>' .
                          '<h5>Movies Forever</h5>' .
                          '<p>Cinema is a kind of art which is a moving picture created with film or digital technologies.</p>' .
                          '<blockquote>' .
                              '<p>A movie is an individual part of a movie which can be divided into episodes or series.</p>' .
                          '</blockquote>' .
                          '<h5>Do you know...</h5>' .
                          '<p>Movies can be of different genres, such as comedies, dramas, thrillers, horror films, etc.</p>' .
                          '<blockquote>' .
                              '<p><small>They can be shot in different countries and in different languages, but they all have common features, such as plot, actors, scenery and music.</small></p>' .
                          '</blockquote>' .
                          '<h5>More info</h5>' .
                          '<p>Movies can be shown in cinemas, on television or on the Internet. They are one of the most popular types of art and have a huge impact on culture and society as a whole.</p>' .
                          '<p><small>* This page is a demo post designed to display page content for testers. You need <a href="news/demo-posts/with-title-and-short-text-and-full-text" rel="nofollow">delete this page</a> when you run the website in production mode.</small></p>',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 19:02:14'
        ], [
            'url'      => 'demo-posts/fact-1',
            'title'    => 'Did you know this fact?',
            'meta'     => 'If a post is being moderated, it will blink with text "It is not visible now!" and no one except the administrator will see this post.',
            'enabled'  => FALSE,
            'created'  => '2023-11-27 18:51:03'
        ], [
            'url'      => 'demo-posts/faq',
            'meta'     => 'Great! Let\'s now write a few posts below, which will become a small dictionary of Frequently Asked Questions.',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 18:39:52'
        ], [
            'url'      => 'demo-posts/faq/how-to-install',
            'title'    => 'How to install this web app?',
            'meta'     => 'To do this, you just need to follow a few simple steps. Let\'s describe their sequence below on the page of this post.',
            'image'    => 'media/demo-posts/tiny-news-feed/image-7.jpg',
            'body'     => '<p>First, you need to download the installation file from the official website of <code>MiMiMi Framework</code>. For example, please download version <a href="https://mimimi.software/media/mimimi-framework-5.2.zip" rel="nofollow">no lower than 5.2</a>, in which this application appeared.</p>' .
                          '<h4>Unpack</h4>' .
                          '<p>The downloaded file is a ZIP archive. Therefore, you need to unpack the contents of that archive to your hosting where you are going to install this web application. Let\'s say, for example, that your hosting is called <code>http://localhost/</code>.</p>' .
                          '<blockquote>' .
                              '<p><small>By the way, if you want to install the <code>Tiny News Feed</code> app not in the root of your hosting, but in its specific folder, you can do this without problems. Let\'s assume the folder will be <code>http://localhost/test/</code>.</small></p>' .
                          '</blockquote>' .
                          '<h4>Launch</h4>' .
                          '<p>Now you should open your browser to the appropriate page of your hosting to begin the installation process of <code>Tiny News Feed</code>.</p>' .
                          '<img src="media/demo-posts/tiny-news-feed/install-1.png">' .
                          '<p>Then you should choose an APP variant as shown in the picture below. This variant is <code>tiny.news.feed</code>.</p>' .
                          '<img src="media/demo-posts/tiny-news-feed/install-2.png">' .
                          '<p>When it comes time to select system modules to install, it is enough to check only the <code>Db</code>, <code>Helper</code> and <code>Url</code> options if you want to install the app in the most minimal configuration.</p>' .
                          '<img src="media/demo-posts/tiny-news-feed/install-3.png">' .
                          '<p>Next, you can specify access codes for the existing database on your server. The following picture shows an example for a MySQL database.</p>' .
                          '<img src="media/demo-posts/tiny-news-feed/install-4.png">' .
                          '<p>In the last screenshot example, we assumed that a user with login <code>root</code> and password <code>******</code> already exists on your database server and a database named <code>my_test_db</code> has been created.</p>' .
                          '<h4>Results</h4>' .
                          '<p>After installing the <code>Tiny News Feed</code> with the same settings as shown in the screenshots above, your hosting will contain the following set of directories.</p>' .
                          '<pre><small>' .
                          "http://localhost/\r\n" .
                          "        ├─&gt; media\r\n" .
                          "        │   └─&gt; demo-posts\r\n" .
                          "        │       └─&gt; tiny-news-feed\r\n" .
                          "        ├─&gt; mimimi.core\r\n" .
                          "        │   ├─&gt; Has\r\n" .
                          "        │   └─&gt; Themes\r\n" .
                          "        │       └─&gt; default\r\n" .
                          "        │           ├─&gt; css\r\n" .
                          "        │           ├─&gt; images\r\n" .
                          "        │           └─&gt; js\r\n" .
                          "        ├─&gt; mimimi.modules\r\n" .
                          "        │   ├─&gt; Db\r\n" .
                          "        │   ├─&gt; Helper\r\n" .
                          "        │   └─&gt; Url\r\n" .
                          "        └─&gt; mimimi.app\r\n" .
                          "            ├─&gt; Helper\r\n" .
                          "            ├─&gt; Router\r\n" .
                          "            ├─&gt; Themes\r\n" .
                          "            │   └─&gt; default\r\n" .
                          "            │       ├─&gt; css\r\n" .
                          "            │       ├─&gt; images\r\n" .
                          "            │       ├─&gt; js\r\n" .
                          "            │       └─&gt; snippets\r\n" .
                          "            └─&gt; Viewers\r\n" .
                          "                ├─&gt; News\r\n" .
                          '                └─&gt; Search' .
                          '</small></pre>' .
                          '<h4>Configure</h4>' .
                          '<p>Now you should configure the website using three constants located in the <code>http://localhost/mimimi.app/Constants.php</code> file.</p>' .
                          '<p>The first constant named <code>TINYFEED_ON_DEMO_NOW</code> is used to switch your site from demo mode to production mode.</p>' .
                          '<p>For example, let\'s switch the website to production mode.</p>' .
                          '<pre>' .
                              'define( \'TINYFEED_ON_DEMO_NOW\', FALSE );' .
                          '</pre>' .
                          '<p>The second constant named <code>TINYFEED_ADMIN_IPS</code> lists your IP addresses to further recognize you as the system administrator.</p>' .
                          '<p>For example, let\'s allow admininstrative visits from the local computer with IP address 127.0.0.1 or address ::1 written in IPv6 format or address mnemonized as localhost.</p>' .
                          '<pre>' .
                              'define( \'TINYFEED_ADMIN_IPS\', [ \'127.0.0.1\', \'::1\', \'localhost\' ] );' .
                          '</pre>' .
                          '<p>The third constant named <code>TINYFEED_HTMLPAGE_LANGUAGE</code> specifies which language is used to write the content of web pages. It has the format "language" or "language-REGION".</p>' .
                          '<p>Valid language and region codes can be found in the <a href="https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry" rel="nofollow">Language Subtag Registry</a> (see the "Type: language" entries for language codes and the "Type: region" entries for region codes).</p>' .
                          '<p>For example, let\'s set the language to Spanish only.</p>' .
                          '<pre>' .
                              'define( \'TINYFEED_HTMLPAGE_LANGUAGE\', \'es\' );' .
                          '</pre>' .
                          '<p>Another example. Let\'s set the Ukrainian language for Russian-speaking regions.</p>' .
                          '<pre>' .
                              'define( \'TINYFEED_HTMLPAGE_LANGUAGE\', \'uk-RU\' );' .
                          '</pre>' .
                          '<p>That\'s all!</p>' .
                          '<p><small>* This page is a demo post designed to display page content for testers. You need <a href="news/demo-posts/faq/how-to-install/delete" rel="nofollow">delete this page</a> when you run the website in production mode.</small></p>',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 18:28:41'
        ], [
            'url'      => 'demo-posts/faq/how-to-get-latest-pages',
            'title'    => 'How to get latest pages?',
            'meta'     => 'In this example, we will read the list of latest publications and put it into the variable $TEST. We will then iterate through this list to print its sublists.',
            'image'    => 'media/demo-posts/tiny-news-feed/image-4.png',
            'body'     => '<p>You can use this example to read database records that should be shown as latest publications.</p>' .
                          '<hr>' .
                          '<h4>Source Code</h4>' .
                          '<p>Please paste this code snippet into your template to see how it works.</p>' .
                          '<blockquote>' .
                              '<p><small>Let\'s experiment with a template like <code>tiny.news.feed/Themes/default/404.tpl</code> by pasting code into it and then opening some <a href="some-indefinite-vague-non-existent-abracadabra" rel="nofollow">non-existent page</a> associated with that template.</small></p>' .
                          '</blockquote>' .
                          '<pre>' .
                          "&lt;?php\r\n" .
                          "    \$TEST = \$app-&gt;getLatest();\r\n" .
                          "    foreach ( \$TEST as \$type =&gt; \$sublist ) {\r\n" .
                          "        if ( \$sublist ) {\r\n" .
                          "            echo 'Publications of kind ' . \$type . ' &lt;br&gt;';\r\n" .
                          "            echo '---------------------------------- &lt;br&gt;';\r\n" .
                          "            foreach ( \$sublist as \$item ) {\r\n" .
                          "                echo \$item['title'] . ' &lt;br&gt;';\r\n" .
                          "            }\r\n" .
                          "        }\r\n" .
                          "    }\r\n" .
                          '?&gt;' .
                          '</pre>' .
                          '<blockquote>' .
                              '<p><small>Note that the <code>$app</code> variable is a system pointer to your web application. And <code>getLatest</code> is a public method for reading the latest publications. If you are interested in the algorithm of this method, look at the file <code>tiny.news.feed/Application.php</code>.</small></p>' .
                          '</blockquote>' .
                          '<h4>Remark</h4>' .
                          '<p>The public method used above has two optional arguments:</p>' .
                          '<ul>' .
                              '<li>' .
                                  'The <code>$size</code> parameter is intended to specify the capacity of each sublist.<br>' .
                                  '<small>The default size is 6 (that is, 2 * 3 publications).</small>' .
                              '</li>' .
                          '</ul>' .
                          '<ul>' .
                              '<li>' .
                                  'The <code>$filter</code> parameter is intended to provide a list containing only names of specific kind.<br>' .
                                  '<small>The default value is an empty list for reading the latest publications of any kind.</small>' .
                              '</li>' .
                          '</ul>' .
                          '<p>The return value is a name-indexed list of sublists retrieved from the database.</p>' .
                          '<hr>' .
                          '<h4>How to get specific latest pages?</h4>' .
                          '<p>In the following code snippet, we will read a list of 4 latest <code>news</code> and 4 <code>articles</code> and put it into the variable $TEST. We will then iterate through this list to print its sublists.' .
                          '<pre>' .
                          "&lt;?php\r\n" .
                          "    \$TEST = \$app-&gt;getLatest(4, [ 'news', 'articles' ]);\r\n" .
                          "    foreach ( \$TEST as \$type =&gt; \$sublist ) {\r\n" .
                          "        if ( \$list ) {\r\n" .
                          "            echo 'Publications of kind ' . \$type . ' &lt;br&gt;';\r\n" .
                          "            echo '---------------------------------- &lt;br&gt;';\r\n" .
                          "            foreach ( \$sublist as \$item ) {\r\n" .
                          "                echo \$item['title'] . ' &lt;br&gt;';\r\n" .
                          "            }\r\n" .
                          "        }\r\n" .
                          "    }\r\n" .
                          '?&gt;' .
                          '</pre>' .
                          '<p><small>* This page is a demo post designed to display page content for testers. You need <a href="news/demo-posts/faq/how-to-get-latest-pages/delete" rel="nofollow">delete this page</a> when you run the website in production mode.</small></p>',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 18:22:35'
        ], [
            'url'      => 'demo-posts/faq/how-to-get-current-page-url',
            'title'    => 'Page URL. How to get it?',
            'meta'     => 'Do you need to get the URL of the current page? Okay, let us look at this action with a small example.',
            'image'    => 'media/demo-posts/tiny-news-feed/image-5.jpg',
            'body'     => '<h4>Source Code</h4>' .
                          '<p>Please paste any of these code snippets into your template to see how it works.</p>' .
                          '<blockquote>' .
                              '<p><small>Let\'s experiment with a template like <code>tiny.news.feed/Themes/default/404.tpl</code> by pasting that code into it and then opening some <a href="some-indefinite-vague-non-existent-abracadabra" rel="nofollow">non-existent page</a> associated with this template.</small></p>' .
                          '</blockquote>' .
                          '<pre>' .
                          "&lt;?php\r\n" .
                          "    \$TEST = mimimiUri(FALSE);\r\n" .
                          "    echo 'The relative page URL is ' . \$TEST;\r\n" .
                          '?&gt;' .
                          '</pre>' .
                          '<blockquote>' .
                              '<p><small>Note that we are used above one of the core routines to retrieve URL path. If you are interested in its algorithm, please see the <code>mimimi.core/Routines.php</code> file.</small></p>' .
                          '</blockquote>' .
                          '<p><b>Way 2</b></p>' .
                          '<pre>' .
                          "&lt;?php\r\n" .
                          "    \$TEST = mimimiSite(FALSE) .\r\n" .
                          "            mimimiRoot(FALSE) .\r\n" .
                          "            mimimiUri(FALSE);\r\n" .
                          "    echo 'The absolute page URL is ' . \$TEST;\r\n" .
                          '?&gt;' .
                          '</pre>' .
                          '<blockquote>' .
                              '<p><small>Note that we are used above 3 core routines to assemble URL from its parts. If you are interested in their algorithmes, please see the <code>mimimi.core/Routines.php</code> file.</small></p>' .
                          '</blockquote>' .
                          '<p><b>Way 3</b></p>' .
                          '<pre>' .
                          "&lt;?php\r\n" .
                          "    \$TEST = printPageUrl(FALSE);\r\n" .
                          "    echo 'The absolute page URL is ' . \$TEST;\r\n" .
                          '?&gt;' .
                          '</pre>' .
                          '<blockquote>' .
                              '<p><small>Note that we are used above one of the helper routines to get all URL parts at once. Its algorithm look at the basic Helper module, that is <code>mimimi.modules/Helper/Helper.php</code> file.</small></p>' .
                          '</blockquote>' .
                          '<h4>Remark</h4>' .
                          '<p>If you want to program specific helper routines to be used in website templates, you can do it anywhere. For example, open local Helper module to see how another routines has been programmed.</p>' .
                          '<p><small>* This page is a demo post to display a page content for testers. You need <a href="news/demo-posts/faq/how-to-get-current-page-url/delete" rel="nofollow">delete this page</a> when you launch website in production mode.</small></p>',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 18:17:30'
        ], [
            'url'      => 'demo-posts/faq/how-to-get-names-of-installed-viewers',
            'title'    => 'How to get names of installed viewers?',
            'meta'     => 'In this example, we will read the list of installed viewers and put it into the variable $TEST. We will then iterate through this list to print its elements.',
            'image'    => 'media/demo-posts/tiny-news-feed/image-6.jpg',
            'body'     => '<p>You can use this example to get all the names of the viewers installed in your web application.</p>' .
                          '<hr>' .
                          '<h4>Theory</h4>' .
                          '<p>Note that a viewer is a module designed to read specific database records located at the URLs associated with that viewer.</p>' .
                          '<blockquote>' .
                              '<p><small>The first segment in such URLs is always equal to the viewer name. For example, the locator <code>https://your.site/news/some-url-path</code> is associated with the <code>News</code> viewer.</small></p>' .
                          '</blockquote>' .
                          '<p>All viewer modules are located at a common folder named <code>tiny.news.feed/Viewers</code>. Each viewer is a named subfolder (for example, <code>tiny.news.feed/Viewers/Search</code>) and a nested PHP file of the same name (for example, <code>tiny.news.feed/Viewers/Search/Search.php</code>) with a certain class and methods declared there.</p>' .
                          '<h4>Source Code</h4>' .
                          '<p>Please paste this code snippet into your template to see how it works.</p>' .
                          '<blockquote>' .
                              '<p><small>Let\'s experiment with a template like <code>tiny.news.feed/Themes/default/404.tpl</code> by pasting code into it and then opening some <a href="some-indefinite-vague-non-existent-abracadabra" rel="nofollow">non-existent page</a> associated with that template.</small></p>' .
                          '</blockquote>' .
                          '<pre>' .
                          "&lt;?php\r\n" .
                          "    \$TEST = \$app-&gt;getViewers();\r\n" .
                          "    foreach ( \$TEST as \$name ) {\r\n" .
                          "        echo \$name . ' &lt;br&gt;';\r\n" .
                          "    }\r\n" .
                          '?&gt;' .
                          '</pre>' .
                          '<blockquote>' .
                              '<p><small>Note that the <code>$app</code> variable is a system pointer to your web application. And <code>getViewers</code> is a public method for reading the viewer names. If you are interested in the algorithm of this method, look at the file <code>tiny.news.feed/Application.php</code>.</small></p>' .
                          '</blockquote>' .
                          '<p>And <a href="news/demo-posts/faq/how-to-get-names-of-relevant-viewers">here</a> you can read the answer to another question regarding reading a list of viewers that support only specific functionality.</p>' .
                          '<p><small>* This page is a demo post designed to display page content for testers. You need <a href="news/demo-posts/faq/how-to-get-names-of-installed-viewers/delete" rel="nofollow">delete this page</a> when you run the website in production mode.</small></p>',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 18:06:19'
        ], [
            'url'      => 'demo-posts/faq/how-to-get-names-of-relevant-viewers',
            'title'    => 'How to get names of relevant viewers?',
            'meta'     => 'In this example, we will read the list of viewers with the sitemap functionality and put it into the variable $TEST. We will then iterate through this list to print its elements.',
            'image'    => 'media/demo-posts/tiny-news-feed/image-7.jpg',
            'body'     => '<p>You can use this example to get all viewer names that support some functionality.</p>' .
                          '<hr>' .
                          '<h4>Theory</h4>' .
                          '<p>Note that a viewer is a module designed to read specific database records located at the URLs associated with that viewer.</p>' .
                          '<blockquote>' .
                              '<p><small>The first segment in such URLs is always equal to the viewer name. For example, the locator <code>https://your.site/news/some-url-path</code> is associated with the <code>News</code> viewer.</small></p>' .
                          '</blockquote>' .
                          '<blockquote>' .
                              '<p>' .
                                  '<small>' .
                                      'And next example. Any locators like listed below are associated with the <code>Search</code> viewer:' .
                                      '<br>&bull; <code>https://your.site/search</code>,' .
                                      '<br>&bull; <code>https://your.site/search/some-page-number</code>,' .
                                      '<br>&bull; <code>https://your.site/search?q=some-query</code>,' .
                                      '<br>&bull; <code>https://your.site/search/some-page-number?q=some-query</code>,' .
                                      '<br>&bull; <code>https://your.site/search/some-url-path</code>,' .
                                      '<br>&bull; <code>https://your.site/search/some-url-path/some-page-number</code>.' .
                                  '</small>' .
                              '</p>' .
                          '</blockquote>' .
                          '<p>However, a viewer module may provide full or partial functionality depending on the goals of its developer. In this case, for some pages of the website, it may be important to get a list of viewers that correspond to the functionality of this page.</p>' .
                          '<p>Therefore, unsuitable viewers are excluded from the resulting list if they do not have a template in the wesite theme that corresponds to such functionality.</p>' .
                          '<blockquote>' .
                              '<p><small>To exclude unsuitable viewers, you need specify a relevancy pattern as an input parameter to the <code>getViewers</code> method.</small></p>' .
                          '</blockquote>' .
                          '<h4>Source Code</h4>' .
                          '<p>Please paste this code snippet into your template to see how it works.</p>' .
                          '<blockquote>' .
                              '<p><small>Let\'s experiment with a template like <code>tiny.news.feed/Themes/default/404.tpl</code> by pasting code into it and then opening some <a href="some-indefinite-vague-non-existent-abracadabra" rel="nofollow">non-existent page</a> associated with that template.</small></p>' .
                          '</blockquote>' .
                          '<pre>' .
                          "&lt;?php\r\n" .
                          "    \$TEST = \$app-&gt;getViewers('*-sitemap.tpl');\r\n" .
                          "    foreach ( \$TEST as \$name ) {\r\n" .
                          "        echo \$name . ' &lt;br&gt;';\r\n" .
                          "    }\r\n" .
                          '?&gt;' .
                          '</pre>' .
                          '<blockquote>' .
                              '<p><small>Note that the <code>$app</code> variable is a system pointer to your web application. And <code>getViewers</code> is a public method for reading the viewer names. If you are interested in the algorithm of this method, look at the file <code>tiny.news.feed/Application.php</code>.</small></p>' .
                          '</blockquote>' .
                          '<p><small>* This page is a demo post designed to display page content for testers. You need <a href="news/demo-posts/faq/how-to-get-names-of-relevant-viewers/delete" rel="nofollow">delete this page</a> when you run the website in production mode.</small></p>',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 17:55:08'
        ], [
            'url'      => 'demo-posts/faq/how-to-get-sitemap-urls',
            'title'    => 'How to get sitemap URLs?',
            'meta'     => 'In this example, we will read the list of non-disabled URLs of news pages and put it into the variable $TEST. We will then iterate through this list to print its elements.',
            'image'    => 'media/demo-posts/tiny-news-feed/image-8.jpg',
            'body'     => '<h4>Source Code</h4>' .
                          '<p>Please paste this code snippet into your template to see how it works.</p>' .
                          '<blockquote>' .
                              '<p><small>Let\'s experiment with a template like <code>tiny.news.feed/Themes/default/404.tpl</code> by pasting code into it and then opening some <a href="some-indefinite-vague-non-existent-abracadabra" rel="nofollow">non-existent page</a> associated with that template.</small></p>' .
                          '</blockquote>' .
                          '<pre>' .
                          "&lt;?php\r\n" .
                          "    \$TEST = \$app-&gt;getSitemap('news');\r\n" .
                          "    \$site = printSiteUrl(FALSE);\r\n" .
                          "    foreach ( \$TEST as \$item ) {\r\n" .
                          "        echo \$site . \$item['prefix'] . \$item['url'] . ' &lt;br&gt;';\r\n" .
                          "    }\r\n" .
                          '?&gt;' .
                          '</pre>' .
                          '<blockquote>' .
                              '<p><small>Note that the <code>$app</code> variable is a system pointer to your web application. And <code>getSitemap</code> is a public method for reading non-disabled URLs. If you are interested in the algorithm of this method, look at the file <code>tiny.news.feed/Application.php</code>.</small></p>' .
                          '</blockquote>' .
                          '<p><small>* This page is a demo post designed to display page content for testers. You need <a href="news/demo-posts/faq/how-to-get-sitemap-urls/delete" rel="nofollow">delete this page</a> when you run the website in production mode.</small></p>',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 17:43:57'
        ], [
            'url'      => 'demo-posts/faq/ask-us',
            'title'    => 'Do you have a question?',
            'meta'     => 'Ask the author of this web application. And perhaps he will write a corresponding demo post here if your question interests everyone.',
            'enabled'  => TRUE,
            'created'  => '2023-11-27 17:32:46'
        ]
    ];

    /**
     * ---------------------------------------------------------------------
     *
     * Renames the record field or changes its value.
     *
     * ---------------------------------------------------------------------
     *
     * The news edit form has a URL field, and the user can fill it with
     * any value, including empty. But the database table has the same
     * column only with a unique non-empty key. So if this URL field is
     * empty, we need to fill it with a unique value (eg random) when
     * saving the form submission as a database row.
     *
     * To understand this logic, please see the siftRecord method of the
     * parent class located in file "mimimi.core/ModuleWithTable.php".
     *
     * ---------------------------------------------------------------------
     *
     * @protected
     * @param      string  $name   The field name to be renamed.
     * @param      mixed   $value  The field value to be changed.
     *
     * ---------------------------------------------------------------------
     */

    protected function renameField ( & $name, & $value ) {
                                                        // -----------------
        switch ( $name ) {                              // what column are we looking at?
            case 'url':                                 //     if column "url"
                if ( empty($value) ) {                  //         if URL is not specified
                    $value = mimimiRandomId(            //             generate random URL ( see file mimimi.core/Routines.php -> mimimiRandomId )
                                 60                     //             . . of this length
                             );                         //
                }                                       //
                break;                                  //
        }                                               //
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Custom routing of the requested page URL.
     *
     * ---------------------------------------------------------------------
     *
     * Please use this method if the right side of the URL needs to be
     * routed in some non-standard way. In this case, you should parse the
     * URL according to some rules for the NEWS module, read the associated
     * database records, prepare the necessary variables, and return the
     * file name of the corresponding template.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   string       $params  (optional) The URL path of the requested page.
     * @return  string|bool           STRING       if we parse the URL ourselves and find the corresponding page template.
     *                                EMPTY STRING if the URL was parsed successfully but an Error404 was encountered.
     *                                FALSE        to parse the URL in a standard way.
     *
     * ---------------------------------------------------------------------
     */

    public function run ( $params = '' ) {              // -----------------
        return FALSE;                                   // no custom routing for this module ( to understand this, look at the file tiny.news.feed/Router/Router.php -> run )
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Retrieves URLs for the sitemap.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   int         $mapsize  (optional) The capacity of sitemap.
     * @return  array|bool            ARRAY on success, each item is an array like this [
     *                                                      'prefix' => 'news/',
     *                                                      'url'    => page URL
     *                                                  ].
     *                                FALSE if URLs are not found.
     *
     * ---------------------------------------------------------------------
     */

    public function getSitemap ( $mapsize = 50000 ) {   // -----------------
        $filter = [                                     // build filter
            't1.enabled'     => TRUE,                   // . . search only non-disabled news
            '! t1.body'      => '',                     // . .    AND only news with non-empty text
                                                        // -----------------
            'select' => [                               // . . parameters for the SELECT clause
                't1.url'     => TRUE,                   // . . . . read table columns "url"
                '"news/"'    => 'prefix'                // . . . . add virtual column "prefix" with value "news/"
            ],                                          // . .
                                                        // -----------------
            'orderby' => [                              // . . parameters for the ORDER BY clause
                't1.created' => 'desc'                  // . . . . sort list in descending order of creation date
            ]                                           //
        ];                                              //
                                                        // -----------------
        return $this->select(                           // select records from database ( see file mimimi.core/ModuleWithTable.php -> select )
                   $filter,                             // . . according to these criteria
                   0,                                   // . . starting from this position
                   $mapsize                             // . . entire sitemap
               );                                       //
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Retrieves the news by its URL.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   string      $url  The URL being searched.
     * @param   bool        $any  (optional) TRUE to retrieve the news even if it is disabled.
     *                                       FALSE to retrieve only enabled news.
     * @return  array|bool        ARRAY on success, it contains a database table row.
     *                            FALSE if that news is not found.
     *
     * ---------------------------------------------------------------------
     */

    public function getItem ( $url, $any = FALSE ) {    // -----------------
        $filter = [                                     // build filter
            't1.url'      => $url,                      // . . search in the URL column with the requested value
                                                        // -----------------
            'select' => [                               // . . parameters for the SELECT clause
                't1.*'    => TRUE,                      // . . . . read all columns of the table
                '"news/"' => 'prefix'                   // . . . . add virtual column "prefix" with value "news/"
            ]                                           // . .
        ];                                              //
                                                        // -----------------
        if ( ! $any ) {                                 // if the "any" parameter is not specified
            $filter['t1.enabled'] = TRUE;               //     supplement the filter: AND only non-disabled news
        }                                               //
                                                        // -----------------
        return $this->select(                           // select a record from database ( see file mimimi.core/ModuleWithTable.php -> select )
                   $filter                              // . . according to these criteria
               );                                       //
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Retrieves news listed on the numbered page.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   int         $page  The requested page number.
     * @param   int         $size  (optional) The capacity of the page. Default size is 12 (that is 3 * 4 news).
     * @param   bool        $any   (optional) TRUE to retrieve news even if they are disabled.
     *                                        FALSE to retrieve only enabled news.
     * @return  array|bool         ARRAY on success, each item is an array like a database table row.
     *                             FALSE if news are not found.
     *
     * ---------------------------------------------------------------------
     */

    public function getItems ( $page, $size = 12, $any = FALSE ) {
                                                        // -----------------
        $filter = [                                     // build filter
            'select' => [                               // . . parameters for the SELECT clause
                't1.*'       => TRUE,                   // . . . . read all columns of the table
                '"news/"'    => 'prefix'                // . . . . add virtual column "prefix" with value "news/"
            ],                                          // . .
                                                        // -----------------
            'orderby' => [                              // . . parameters for the ORDER BY clause
                't1.created' => 'desc'                  // . . . . sort list in descending order of creation date
            ]                                           //
        ];                                              //
                                                        // -----------------
        if ( ! $any ) {                                 // if the "any" parameter is not specified
            $filter['t1.enabled'] = TRUE;               //     supplement the filter: AND only non-disabled news
        }                                               //
                                                        // -----------------
        $offset = ($page - 1) * $size;                  // calculate start position of page frame
                                                        // -----------------
        $items = $this->select(                         // select records from database ( see file mimimi.core/ModuleWithTable.php -> select )
                     $filter,                           // . . according to these criteria
                     $offset,                           // . . starting from this position
                     $size + 1                          // . . entire page plus 1 unnecessary record (to determine if the next page exists)
                 );                                     //
                                                        // -----------------
        return $this->owner->cropList(                  // replace last unnecessary record with FALSE ( see file tiny.news.feed/Viewers/Viewers.php -> cropList )
                   $items,                              // . . in this list
                   $size                                // . . expected page capacity
               );                                       //
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Searches for news listed on the numbered page.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   string      $query  The query string to search for.
     * @param   int         $page   The requested page number.
     * @param   int         $size   (optional) The capacity of the page. Default size is 12 (that is 3 * 4 news).
     * @param   bool        $any    (optional) TRUE to search news even if they are disabled.
     *                                              FALSE to search only enabled news.
     * @return  array|bool          ARRAY on success, each item is an array like a database table row.
     *                              FALSE if news are not found.
     *
     * ---------------------------------------------------------------------
     */

    public function searchBy ( $query, $page, $size = 12, $any = FALSE ) {
                                                        // -----------------
        $result = FALSE;                                // there are no news
        if ( is_string($query) ) {                      // if there is a query
                                                        // -----------------
            $query = $this                              //     parse that query into words
                     ->owner                            //     . . via VIEWERS module ( see file tiny.news.feed/Viewers/Viewers.php -> parseQuery )
                     ->parseQuery(                      //     . . using this method
                         $query,                        //         . . from this string
                         4                              //         . . no more than 4 words
                     );                                 //
                                                        // -----------------
            if ( $query ) {                             //     if the query contains at least one word
                $filter = [                             //         build filter
                    '! t1.url'       => '',             //         . . search only news with non-empty URL
                                                        // -----------------
                    'select' => [                       //         . . parameters for the SELECT clause
                        't1.*'       => TRUE,           //         . . . . read all columns of the table
                        '"news/"'    => 'prefix'        //         . . . . add virtual column "prefix" with value "news/"
                    ],                                  //         . .
                                                        // -----------------
                    'orderby' => [                      //         . . parameters for the ORDER BY clause
                        't1.created' => 'desc'          //         . . . . sort list in descending order of creation date
                    ]                                   //
                ];                                      //
                                                        // -----------------
                if ( ! $any ) {                         //         if the "any" parameter is not specified
                    $filter['t1.enabled'] = TRUE;       //             supplement the filter: AND only non-disabled news
                }                                       //
                                                        // -----------------
                $endTitle = '';                         //         make the WHERE clause endings
                $endMeta  = '';                         //
                $endBody  = '';                         //
                foreach ( $query as $word ) {           //
                    $word = preg_replace('~[%_?"\\\\]~u', '', $word);
                    if ( $word ) {
                        $endTitle .= ' AND `t1`.`title` LIKE "%' . $word . '%"';
                        $endMeta  .= ' AND `t1`.`meta` LIKE "%'  . $word . '%"';
                        $endBody  .= ' AND `t1`.`body` LIKE "%'  . $word . '%"';
                    }
                }
                                                        // -----------------
                $filter['+'] = 'AND (' .                //         supplement the filter to end the WHERE clause
                    '`t1`.`title` != ""'   . $endTitle .//         . . with such a statement for "title" column
                    ' OR `t1`.`meta` != ""' . $endMeta .//         . . with such a statement for "meta" column
                    ' OR `t1`.`body` != ""' . $endBody .//         . . with such a statement for "body" column
                ') ';                                   //
                                                        // -----------------
                $offset = ($page - 1) * $size;          //         calculate start position of page frame
                                                        // -----------------
                $result = $this->select(                //         select records from database ( see file mimimi.core/ModuleWithTable.php -> select )
                              $filter,                  //         . . according to these criteria
                              $offset,                  //         . . starting from this position
                              $size + 1                 //         . . entire page plus 1 unnecessary record (to determine if the next page exists)
                          );                            //
                                                        // -----------------
                $result = $this->owner->cropList(       //         replace last unnecessary record with FALSE ( see file tiny.news.feed/Viewers/Viewers.php -> cropList )
                              $result,                  //         . . in this list
                              $size                     //         . . expected page capacity
                          );                            //
            }                                           //
        }                                               //
                                                        // -----------------
        return $result;                                 // return a list of news
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Retrieves the latest news.
     *
     * ---------------------------------------------------------------------
     *
     * This method just wraps the getItems() method declared above to read
     * the desired amount of news from the first page of their list. Keep
     * in mind that the list obtained by the above method is always sorted
     * by the date the news was created. See the ORDER BY clause of that
     * method for details. Therefore, reading the latest news is always
     * done starting from the first page of the list.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   int         $size  (optional) The capacity of the list. Default size is 6 (that is 2 * 3 news).
     * @param   bool        $any   (optional) TRUE to retrieve news even if they are disabled.
     *                                        FALSE to retrieve only enabled news.
     * @return  array|bool         ARRAY on success, each item is an array like a database table row.
     *                             FALSE if news are not found.
     *
     * ---------------------------------------------------------------------
     */

    public function getRecent ( $size = 6, $any = FALSE ) {
                                                        // -----------------
        return $this->getItems(                         // get news list
                   1,                                   // . . located on the first page
                   $size,                               // . . expected list capacity
                   $any                                 // . . under this condition
               );                                       //
    }                                                   // -----------------

    /**
     * ---------------------------------------------------------------------
     *
     * Checks for the conflict.
     *
     * ---------------------------------------------------------------------
     *
     * @public
     * @param   string  $by     Column name to search for the value.
     * @param   string  $value  The value of column being tested.
     * @param   int     $id     (optional) The identifier of the news being edited.
     * @return  bool            TRUE  if that value is okay.
     *                          FALSE if a conflict is found.
     *
     * ---------------------------------------------------------------------
     */

    public function testBy ( $by, $value, $id = 0 ) {   // -----------------
        $result = TRUE;                                 // there is OK status
                                                        // -----------------
        if ( $value ) {                                 // if value is specified
            $filter = [                                 //     build filter
                't1.' . $by => $value,                  //     . . search in the requested column with the requested value
                                                        // -----------------
                'select' => [                           //     . . parameters for the SELECT clause
                    '1'     => 'test',                  //     . . . . add virtual column "test" with value 1
                ]                                       //     . .
            ];                                          //
                                                        // -----------------
            if ( $id ) {                                //     if identifier is specified
                $filter['! t1.id'] = $id;               //         supplement the filter: AND except for the record with this identifier
            }                                           //
                                                        // -----------------
            $result = FALSE == $this->select(           //     try selecting a record from database ( see file mimimi.core/ModuleWithTable.php -> select )
                                   $filter              //     . . according to these criteria
                               );                       //
        }                                               //
                                                        // -----------------
        return $result;                                 // return a status
    }                                                   // -----------------
};