<?php
/**
 * -------------------------------------------------------------------------
 *
 * Template for rendering a home page.
 *
 * -------------------------------------------------------------------------
 *
 * The first lines of this template pass the necessary headers to the user's
 * browser. To do this, routines such as "sendHeaderHTML", "sendStatus200",
 * and "stopIfHead" are used. They are implemented in the "Helper" system
 * module. It is located in the "mimimi.modules/Helper/Helper.php" file.
 *
 * The next lines generate a page content. Two routines used here are
 * "printSiteUrl" and "printThemeUrl". They are also implemented in the
 * "Helper" module.
 *
 * -------------------------------------------------------------------------
 */

    sendHeaderHTML ( );
    sendStatus200  ( );
    stopIfHead     ( );

?><!DOCTYPE html>
<html lang="en-US" class="home-page">
    <head>
        <base href="<?php printSiteUrl ( ) ?>">
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="robots"   content="index, follow">
        <title>
            Welcome to Five Viewers demo!
        </title>
        <meta name="description"  content="This web app is an example of how you can build a simple website.">
        <link rel="canonical"     href="<?php printSiteUrl  ( ) ?>">
        <link rel="stylesheet"    href="<?php printThemeUrl ( ) ?>css/styles.css">
        <link rel="shortcut icon" href="<?php printThemeUrl ( ) ?>images/favicon.ico" type="image/ico">
    </head>

    <body>
        <div class="page">
            <?php mimimiModule ( 'snippets/header.tpl' ) ?>
            <?php mimimiModule ( 'snippets/menu.tpl'   ) ?>

            <main class="content">
                <section class="message">
                    <h1>Hi there!</h1>
                    <p>Welcome to <b>Five Viewers</b> demo! This web application is an example of how you can build a simple website containing 5 viewer modules and 1 poller module.</p>
                </section>
                <section class="message">
                    <h4>What do these terms mean?</h4>
                </section>
                <section class="message light half">
                    <p><b>Viewer</b> is a special module corresponding to a URL. The goal of viewers is to generate static content associated with a specific type of URL. This could be an HTML page, an XML document, a plain text document, and so on.</p>
                </section>
                <section class="message light half">
                    <p><b>Poller</b> is similar to a viewer module. It is also a special module, and it also matches a specific URL. But the purpose of pollers is to generate dynamic content, such as JSON for example.</p>
                </section>

                <section class="message hr">
                    <h2>Let's get started</h2>
                    <p>Imagine you want to create a website with:</p>
                    <ul>
                        <li>a <u>home</u> page,</li>
                        <li>
                            a <u>list</u> of channels
                            <ul>
                                <li>plus their <u>individual</u> pages,</li>
                            </ul>
                        </li>
                        <li>an <u>Error404</u> page,</li>
                        <li>a <u>robots.txt</u> document,</li>
                        <li>a <u>sitemap</u> XML document,</li>
                        <li>and a <u>checkpoint</u> which is an API interface, that is, dynamically requests a random message from random channel via Javascript, for example once every 10 seconds.</li>
                    </ul>
                </section>
                <section class="message light half">
                    <p>As a result, 5 types of web pages and 1 periodically polled checkpoint were identified. Let's call each type by some conventional name. It will become the name of the corresponding viewer or poller module.</p>
                    <ol>
                        <li><u>Home</u></li>
                        <li><u>Channels</u></li>
                        <li><u>Error404</u></li>
                        <li><u>RobotsTxt</u></li>
                        <li><u>Sitemap</u></li>
                        <li><u>Api</u></li>
                    </ol>
                </section>
                <section class="message light half">
                    <p>In accordance with these page types, 6 types of URLs were also allocated.</p>
                    <ol>
                        <li><u title="https://your.site/"></u></li>
                        <li><u title="https://your.site/channels"></u>
                            <br><u title="https://your.site/channels/CHANNEL-URL"></u></li>
                        <li><u title="https://your.site/UNDEFINED-URL"></u></li>
                        <li><u title="https://your.site/robots.txt"></u></li>
                        <li><u title="https://your.site/sitemap"></u></li>
                        <li><u title="https://your.site/polls/api/v1/random"></u>
                            <br><u title="https://your.site/polls/api/v1/random/from/CHANNEL-ID"></u></li>
                    </ol>
                </section>
                <section class="message light">
                    <p>Please note that the last URL already has a segment <u>polls</u> linking it to a poller module. The module name <b>Api</b> written after that segment.</p>
                </section>
                <section class="message thin">
                    <p>Also note that the second URL contains a segment <u>channels</u> linking it to a viewer module called <b>Channels</b>.</p>
                </section>
                <section class="message thin">
                    <p>The fourth URL contains a segment <u>robots.txt</u> linking it to a viewer module called <b>RobotsTxt</b>.</p>
                </section>
                <section class="message thin">
                    <p>And finally, the fifth URL contains a segment <u>sitemap</u> linking it to a viewer module called <b>Sitemap</b>.</p>
                </section>
                <section class="message light">
                    <p>Other types of URLs are invalid and automatically link to a viewer module called <b>Error404</b>.</p>
                </section>

                <section class="message interlude hr">
                    <h2>Okay</h2>
                    <p>If you are a developer, you should now go through all the steps of web app creation.</p>
                    <p>In fact, these steps have already been completed, and their result has been added to the <b>MiMiMi</b> framework installation package. You can download its latest version and look at the <u>five.viewers</u> folder there.</p>
                    <nav class="buttons">
                        <a class="btn" href="https://mimimi.software/download/latest" rel="nofollow">
                            Download Latest
                        </a>
                    </nav>
                    <p>But for learning purposes, imagine that such steps have yet to be taken. This approach will allow you to look at the process of creating a web application from the developer's point of view.</p>
                    <p>So let's look at each step separately.</p>
                    <figure>
                        <img src="media/demo-posts/five.viewers/home-1.jpg" alt="">
                        <figcaption>
                            <a href="https://unsplash.com/photos/zwd435-ewb4" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">
                                Photo by Andrea De Santis on Unsplash
                            </a>
                        </figcaption>
                    </figure>
                    <p>The development will be done below in PHP 7.3+ using a MySQL database. And you should begin by writing the main module of your application.</p>
                </section>

                <section class="message hr">
                    <h1><b>Step 1</b> create an application</h1>
                    <p>Please download the <b>MiMiMi</b> framework from the official site and extract it to your computer.</p>
                    <div class="message light">
                        <p>Let me remind that the <u>five.viewers</u> folder already exists there, so either delete it first or pretend it doesn't exist.</p>
                    </div>
                    <p>So you will see a folder structure like this.</p>
                    <pre class="filelist">├─> [+] <del>five.viewers</del>
├─> [+] <strong>media</strong>
├─> [+] <strong>mimimi.core</strong>
├─> [+] <strong>mimimi.install</strong>
├─> [+] <strong>mimimi.modules</strong>
├─> [+] <strong>newspaper</strong>
├─> [+] <strong>tiny.news.feed</strong>
├─> <u>.htaccess</u>
├─> <u>favicon.ico</u>
└─> <u>index.php</u></pre>
                    <h4>What is each folder for?</h4>
                    <ul class="cellular">
                        <li><del>five.viewers</del> is a folder containing a demo application called <b>Five Viewers</b>.</li>
                        <li><strong>media</strong> is a folder for storing media files used on the pages of your site. The installation package contains in this folder various demo images used in demo applications.</li>
                        <li><strong>mimimi.core</strong> is a folder containing a small set of basic classes and routines that you can use to develop modules for your site.</li>
                        <li><strong>mimimi.install</strong> is a folder containing a web application to install framework components.</li>
                        <li><strong>mimimi.modules</strong> is a folder containing a small set of ready-made modules that can be immediately used on your site.</li>
                        <li><strong>newspaper</strong> is a folder containing a demo application called <b>Newspaper</b>.</li>
                        <li><strong>tiny.news.feed</strong> is a folder containing a demo application called <b>Tiny News Feed</b>.</li>
                    </ul>
                </section>

                <section class="message hr">
                    <h4>Let's begin</h4>
                    <p>Create a folder <u>five.viewers</u> there, and in it a file <u>Application.php</u> with the following contents.</p>
                    <pre data-title="Application.php">&lt;?php
<ins>/** ------------------------------------------------------------------------
 * The main module for receiving requests. It is always called using <em>run()</em>
 * methow implemented in the <u>mimimi.core/Website.php</u> file. The initiator
 * of that call is the root <u>index.php</u> file of your website.
 * ---------------------------------------------------------------------- */</ins>

    <em>mimimiInclude</em> ( '<u>Website.php</u>' );
    class <b>MyMimimiApplication</b> extends <b>MimimiWebsite</b> {

        <ins>/** ----------------------------------------------------------------
         * Define initial options.
         * -------------------------------------------------------------- */</ins>

        protected <i>$viewerCollector</i> = <strong>''</strong>;
        protected <i>$pollerCollector</i> = <strong>''</strong>;
        protected <i>$systemCollector</i> = <strong>''</strong>;
        protected <i>$allowedPollers</i>  = <strong>'api'</strong>;
        protected <i>$allowedViewers</i>  = <strong>'home, channels, error404, robots_txt, sitemap'</strong>;

        <ins>/** ----------------------------------------------------------------
         * Reset namespace simulator.
         * -------------------------------------------------------------- */</ins>

        protected <i>$myNodeFile</i> = __FILE__;
    };</pre>
                    <h4>How does this work?</h4>
                    <ol class="cellular">
                        <li>In the first line, we load a file <u>mimimi.core/Website.php</u> containing a base class <b>MimimiWebsite</b>. It is designed for a web-based application and is a simple router that recognizes the requested viewer module by first segment of URL.</li>
                        <li>Then we extend the base class to the <b>MyMimimiApplication</b> class, which our application will become, and specify the necessary parameters in its properties.</li>
                    </ol>
                </section>
                <section class="message">
                    <h4>What do the properties mean?</h4>
                </section>
                <section class="message light half">
                    <p>The <i>$allowedPollers</i> and <i>$allowedViewers</i> properties define comma-separated names of poller modules or visual modules that can be referenced by URL.</p>
                    <p>This is a kind of security setting that prevents a third-party module from being intentionally launched, even if it was accidentally left in the application folder.</p>
                </section>
                <section class="message light half">
                    <p>The <i>$viewerCollector</i>, <i>$pollerCollector</i>, and <i>$systemCollector</i> properties define module names that will collect all related modules in a collector's folder.</p>
                    <p>Best practices recommend moving modules of the same type into special folder to better organize your app's file structure.</p>
                    <p>We are ignoring this recommendation because it is a demo app.</p>
                </section>

                <section class="message hr">
                    <h4>What does your build look like now?</h4>
                    <pre class="filelist">├─> [-] <strong class="newly">five.viewers</strong>
│       └─> <u class="newly">Application.php</u>
│           <i class="newly">├─&lt;─ property $viewerCollector</i>
│           <i class="newly">├─&lt;─ property $pollerCollector</i>
│           <i class="newly">├─&lt;─ property $systemCollector</i>
│           <i class="newly">├─&lt;─ property $allowedPollers</i>
│           <i class="newly">├─&lt;─ property $allowedViewers</i>
│           <i class="newly">└─&lt;─ property $myNodeFile</i>
├─> [+] <strong>media</strong>
├─> [+] <strong>mimimi.core</strong>
├─> [+] <strong>mimimi.install</strong>
├─> [+] <strong>mimimi.modules</strong>
├─> [+] <strong>newspaper</strong>
├─> [+] <strong>tiny.news.feed</strong>
├─> <u>.htaccess</u>
├─> <u>favicon.ico</u>
└─> <u>index.php</u></pre>
                    <p>A blinking green dot at the end of the file name indicates recently created folders or files. Green strings indicates functions or methods added to the file.</p>
                </section>

                <section class="message interlude hr">
                    <h2>Okay, let's move on</h2>
                    <p>The application module implemented in step 1 above is the central component of your site. Its task is to receive an incoming browser request, parse the URL to identify the associated viewer or poller, and pass control to that module.</p>
                    <p>Therefore, the remaining steps will be devoted to creating viewer modules. According to the initial task, we planned to develop the following set of modules:</p>
                    <ul>
                        <li><b>Home</b> is a viewer module for displaying the contents of the home page.</li>
                        <li><b>Error404</b> is a viewer module for displaying an error page.</li>
                        <li><b>RobotsTxt</b> is a viewer module for generating a <u>robots.txt</u> document.</li>
                        <li><b>Sitemap</b> is a viewer module for generating a sitemap XML document.</li>
                        <li><b>Api</b> is a poller module for requesting a message.</li>
                        <li><b>Channels</b> is a viewer module for displaying a list of channels or a channel page.</li>
                    </ul>
                    <p>In MVC terminology, each viewer module is a thin Controller that accepts URL parameters via method <em>run(<i>$params</i>)</em> and translates them into directives for a Model and View.</p>
                    <p>The Model can, in principle, be represented either by a module working with a database table, or simply by a method of the Controller itself.</p>
                    <p>The View can be represented by a template file located in your application's themes folder. It is the <u>five.viewers/Themes/default</u> folder.</p>
                    <figure>
                        <img src="media/demo-posts/five.viewers/home-2.jpg" alt="">
                        <figcaption>
                            <a href="https://unsplash.com/photos/uoMQ4jM7q9Y" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">
                                Photo by Birmingham Museums Trust on Unsplash
                            </a>
                        </figcaption>
                    </figure>
                    <p>Let's start with the first viewer module. Its name is <b>Home</b>.</p>
                </section>

                <section class="message hr">
                    <h1><b>Step 2</b> create a home page</h1>
                    <p>The whole process consists of four substeps.</p>
                    <ul class="cellular">
                        <li>The first is creating a Controller, that is, writing a program logic that describes how to serve an incoming request from the user's browser.</li>
                        <li>The second substep is creating a Model, that is, writing a logic that describes how to query data from the database if it is needed for this page.</li>
                        <li>The third substep is creating a View, that is, marking up a content template that describes how to send a response to the browser.</li>
                        <li>The fourth substep is also templates called snippets, which describe how to generate some blocks that are repeated on each page.</li>
                    </ul>
                    <p>Well, shall we get started? Okay, let's go.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 2.1: Controller</h4>
                    <p>Please create a subfolder <u>Home</u>, and in it a file <u>Home.php</u> with the following contents.</p>
                    <pre data-title="Home.php">&lt;?php
<ins>/** ------------------------------------------------------------------------
 * The viewer module for displaying a home page. It is always called using
 * <em>run()</em> method implemented below. The initiator of that call is the main
 * module of your application.
 * ---------------------------------------------------------------------- */</ins>

    <em>mimimiInclude</em> ( '<u>Module.php</u>' );
    class <b>MyMimimiHome</b> extends <b>MimimiModule</b> {

        <ins>/** ----------------------------------------------------------------
         * Displays the contents of home page.
         * Only two forms of URL are processed by this method:
         *     https://your.site/
         *     https://your.site/home
         * Another forms are incorrect:
         *     https://your.site/home/UNDEFINED-OPTIONS
         *                            └──────┬────────┘
         *                                   └─&gt; it is the incoming <i>$params</i>
         * @param  mixed <i>$params</i> The rest of page's URL if detected by the app's routing method.
         * @return bool          <i>TRUE</i>  if the page was rendered successfully.
         *                       <i>FALSE</i> if the page URL is incorrect and an Error404 page needs to be displayed.
         * -------------------------------------------------------------- */</ins>

        public function <em>run</em> ( <i>$params</i> = <strong>''</strong> ) {
            return <i>$params</i> == <strong>''</strong>
                &amp;&amp; <b>$this</b>-&gt;<b>app</b>-&gt;<em>renderTemplate</em> ( '<u>home.tpl</u>',
                                                <strong>'Oops, this site does not have a home page template!'</strong> );
        }
    };</pre>
                    <h4>How does this work?</h4>
                    <ol class="cellular">
                        <li>The first line of code loads a file <u>mimimi.core/Module.php</u> containing a base class <b>MimimiModule</b>. It is designed as a miniature basis for any module and declares just two public properties (<i>$app</i>, <i>$owner</i>) and an empty method <em>run()</em>.</li>
                        <li>Then we extend the base class to the <b>MyMimimiHome</b> class, which will become the home page viewer.</li>
                        <li>Next, we write a method algorithm that will generate HTML content for this page, keeping in mind that the method accepts a parameter <i>$params</i> as input, which contains the rest of the requested URL.</li>
                    </ol>
                </section>
                <section class="message light half">
                    <p>Please note that due to the specifics of the app's router, the <em>run()</em> method is called for any root URLs or for any URLs starting with "home".</p>
                    <p>However, only two forms of URL is processed by this method:</p>
                    <ul>
                        <li><u title="https://your.site/"></u></li>
                        <li><u title="https://your.site/home"></u></li>
                    </ul>
                </section>
                <section class="message light half">
                    <p>Another forms of the locator are incorrect and are therefore ignored, causing the app's router to display them later using the visual module <b>Error404</b>. This is what those incorrect forms look like:</p>
                    <ul>
                        <li><u title="https://your.site/home/UNDEFINED-OPTIONS"></u></li>
                    </ul>
                    <p>Ignoring is done using a directive <code><i>$params</i> == <strong>''</strong></code> above.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 2.2: Model</h4>
                    <p>The home page does not require you to query any database entries for it. Therefore, we do nothing at this substep.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 2.3: View</h4>
                    <p>Please markup a template file for rendering HTML content of the home page. Create a subfolder <u>Themes/default</u>, and in it a file <u>home.tpl</u> with the following contents.</p>
                    <pre data-title="home.tpl">&lt;?php
    <ins>/** --------------------------------------------------------------------
     * Send headers to the user's browser.
     * ------------------------------------------------------------------ */</ins>

    <em>sendHeaderHTML</em> ( );
    <em>sendStatus200</em>  ( );
    <em>stopIfHead</em>     ( );

    <ins>/** --------------------------------------------------------------------
     * Generate page content.
     * ------------------------------------------------------------------ */</ins>

?&gt;&lt;!DOCTYPE html&gt;
&lt;html lang="en-US" class="<i>home-page</i>"&gt;
    &lt;head&gt;
        &lt;base href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;"&gt;
        &lt;meta charset="UTF-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
        &lt;meta name="robots"   content="index, follow"&gt;
        &lt;title&gt;
            <strong>Welcome to Five Viewers demo!</strong>
        &lt;/title&gt;
        &lt;meta name="description"  content="<strong>This web app is an example of how you can build a simple website.</strong>"&gt;
        &lt;link rel="canonical"     href="&lt;?php <em>printSiteUrl</em>  ( ) ?&gt;"&gt;
        &lt;link rel="stylesheet"    href="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>css/styles.css</u>"&gt;
        &lt;link rel="shortcut icon" href="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>images/favicon.ico</u>" type="image/ico"&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div class="<i>page</i>"&gt;
            &lt;?php <em>mimimiModule</em> ( '<u>snippets/header.tpl</u>' ) ?&gt;
            &lt;?php <em>mimimiModule</em> ( '<u>snippets/menu.tpl</u>'   ) ?&gt;

            &lt;main class="<i>content</i>"&gt;
                <strong>... an HTML ...</strong>
            &lt;/main&gt;

            &lt;aside class="<i>hint</i>"&gt;
                <strong>... an HTML ...</strong>
            &lt;/aside&gt;

            &lt;?php <em>mimimiModule</em> ( '<u>snippets/footer.tpl</u>' ) ?&gt;
        &lt;/div&gt;
        &lt;script src="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>js/scripts.js</u>"&gt;&lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre>
                    <h4>How does this work?</h4>
                    <ol class="cellular">
                        <li>The first lines of this template pass the necessary headers to user's browser. To do this, routines such as <em>sendHeaderHTML()</em>, <em>sendStatus200()</em>, and <em>stopIfHead()</em> are used. They are implemented in a system module <b>Helper</b>. It is located in a file <u>mimimi.modules/Helper/Helper.php</u></li>
                        <li>The next lines generate a page content. Two routines used here are <em>printSiteUrl()</em> and <em>printThemeUrl()</em>. They are also implemented in the <b>Helper</b> module.</li>
                        <li>There is another routine called <em>mimimiModule()</em>. It is located in a file <u>mimimi.core/Routines.php</u> and is used to include one template file within another.</li>
                    </ol>
                </section>

                <section class="message hr">
                    <h4>Substep 2.4: snippets</h4>
                    <div class="message light">
                        <h5>What are snippets?</h5>
                        <p>They are HTML fragments that repeat on different pages and have the same logic and markup.</p>
                    </div>
                    <p>So you should create a subfolder <u>Themes/default/snippets</u>, and in it the following three files.</p>
                    <p>The first file, named <u>header.tpl</u>, is used to generate the topmost block of the page.</p>
                    <pre data-title="header.tpl">&lt;header class="<i>header</i>"&gt;
    <strong>Five Viewers demo</strong>
&lt;/header&gt;</pre>
                    <p>The second file, named <u>footer.tpl</u>, is used to generate the bottommost block.</p>
                    <pre data-title="footer.tpl">&lt;footer class="<i>footer</i>"&gt;
    &lt;div class="<i>copyright</i>"&gt;
        <strong>&amp;copy; 2024 The Demo Site</strong>
    &lt;/div&gt;
    &lt;div class="<i>credits</i>"&gt;
        <strong>Powered by</strong> &lt;a href="<u>https://mimimi.software/</u>" rel="nofollow" tabindex="-1"&gt;
                       <strong>MiMiMi</strong>
                   &lt;/a&gt;
    &lt;/div&gt;
&lt;/footer&gt;</pre>
                    <p>The third file, named <u>menu.tpl</u>, is used to generate the left sidebar.</p>
                    <pre data-title="menu.tpl">&lt;nav class="<i>menu</i>"&gt;
    &lt;a class="<i>home-link</i>"     href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;"        &gt; <strong>Home</strong>     &lt;/a&gt;
    &lt;a class="<i>channels-link</i>" href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;<u>channels</u>"&gt; <strong>Channels</strong> &lt;/a&gt;

    &lt;a class="<i>error404-link</i>"
       href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;<u>abracadabra</u>"
       rel="nofollow"&gt; <strong>Error 404</strong> &lt;/a&gt;

    &lt;a class="<i>servo</i>"
       href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;<u>sitemap</u>"
       rel="nofollow"
       target="_blank"&gt; <strong>Sitemap</strong> &lt;/a&gt;

    &lt;a class="<i>servo</i>"
       href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;<u>robots.txt</u>"
       rel="nofollow"
       target="_blank"&gt; <strong>Robots.txt</strong> &lt;/a&gt;

    &lt;a class="<i>servo no-gap</i>"
       href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;<u>polls/api/v1/random</u>"
       rel="nofollow"
       target="_blank"
       title="Random message from all database"&gt; <strong>Checkpoint All</strong> &lt;/a&gt;

    &lt;a class="<i>servo</i>"
       href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;<u>polls/api/v1/random/from/1</u>"
       rel="nofollow"
       target="_blank"
       title="Random message from first channel only"&gt; <strong>Checkpoint One</strong> &lt;/a&gt;

    &lt;div&gt;<strong>You can click a checkpoint above to see it as JSON. The <b>&lt;b&gt;Checkpoint All&lt;/b&gt;</b> will also be updated every 10 seconds below to demonstrate how a polling works.</strong>&lt;/div&gt;

    &lt;div class="<i>idle</i>"
         <i>data-poller="<u>api</u>"</i>
         <i>data-version="<u>v1</u>"</i>
         <i>data-command="<u>random</u>"</i>
         <i>data-options=""</i>
         <i>data-timer="10"</i>
         <i>data-success="displayRandom"</i>&gt;&lt;/div&gt;
&lt;/nav&gt;</pre>
                </section>
                <section class="message light half">
                    <p>On a production site, you can remove any DIVs and anchors that contain a class <i>error404-link</i> or <i>servo</i>, as they are used for demostration purposes only.</p>
                </section>
                <section class="message light half">
                    <p>The <i>data-command="<u>random</u>"</i> is an attribute for marking the container that will receive a response after each request to the API with the <u>random</u> command.</p>
                </section>
                <section class="message light">
                    <p>The <i>data-options</i> is an attribute for passing some parameters to that command via URL.</p>
                </section>
                <section class="message light">
                    <p>For example, if <i>data-command="<u>random</u>"</i> and <i>data-options="<u>from/2</u>"</i>, the API request will be made via the <u title="https://your.site/polls/api/v1/random/from/2"></u> locator.</p>
                </section>
                <section class="message">
                    <p>In addition to these server side files, there are 3 more client side files:</p>
                    <ul>
                        <li><u>favicon.ico</u> to assign an icon to your application's shortcut.</li>
                        <li><u>styles.css</u> for defining styles for your app's design.</li>
                        <li><u>scripts.js</u> for declaring scripts for the client side of your website.</li>
                    </ul>
                    <p>You can view any of these files right now.</p>
                    <nav class="buttons">
                        <a class="btn" href="<?php printThemeUrl ( ) ?>css/styles.css" rel="nofollow" target="_blank">
                            See CSS
                        </a>
                        <a class="btn" href="<?php printThemeUrl ( ) ?>js/scripts.js" rel="nofollow" target="_blank">
                            See Javascript
                        </a>
                        <a class="btn" href="<?php printThemeUrl ( ) ?>images/favicon.ico" rel="nofollow" target="_blank">
                            See Favicon
                        </a>
                    </nav>
                </section>

                <section class="message hr">
                    <h4>What does your build look like now?</h4>
                    <pre class="filelist">├─> [-] <strong>five.viewers</strong>
│       ├─> <strong class="newly">Home</strong>
│       │   └─> <u class="newly">Home.php</u>
│       │       <i class="newly">└─&lt;─ run()</i>
│       ├─> <strong class="newly">Themes</strong>
│       │   └─> <strong class="newly">default</strong>
│       │       ├─> <strong class="newly">css</strong>
│       │       │   └─> <u class="newly">styles.css</u>
│       │       ├─> <strong class="newly">images</strong>
│       │       │   └─> <u class="newly">favicon.ico</u>
│       │       ├─> <strong class="newly">js</strong>
│       │       │   └─> <u class="newly">scripts.js</u>
│       │       │       <i class="newly">├─&lt;─ findTimes()</i>
│       │       │       <i class="newly">├─&lt;─ extractTime()</i>
│       │       │       <i class="newly">├─&lt;─ fixTimes()</i>
│       │       │       <i class="newly">├─&lt;─ displayRandom()</i>
│       │       │       <i class="newly">└─&lt;─ renderMessage()</i>
│       │       ├─> <strong class="newly">snippets</strong>
│       │       │   ├─> <u class="newly">footer.tpl</u>
│       │       │   ├─> <u class="newly">header.tpl</u>
│       │       │   └─> <u class="newly">menu.tpl</u>
│       │       └─> <u class="newly">home.tpl</u>
│       └─> <u>Application.php</u>
│           <i>├─&lt;─ property $viewerCollector</i>
│           <i>├─&lt;─ property $pollerCollector</i>
│           <i>├─&lt;─ property $systemCollector</i>
│           <i>├─&lt;─ property $allowedPollers</i>
│           <i>├─&lt;─ property $allowedViewers</i>
│           <i>└─&lt;─ property $myNodeFile</i>
├─> [+] <strong>media</strong>
├─> [+] <strong>mimimi.core</strong>
├─> [+] <strong>mimimi.install</strong>
├─> [+] <strong>mimimi.modules</strong>
├─> [+] <strong>newspaper</strong>
├─> [+] <strong>tiny.news.feed</strong>
├─> <u>.htaccess</u>
├─> <u>favicon.ico</u>
└─> <u>index.php</u></pre>
                    <p>A blinking green dot at the end of the file name indicates recently created folders or files. Green strings indicates functions or methods added to the file.</p>
                </section>

                <section class="message interlude hr">
                    <h2>Great, let's continue</h2>
                    <p>Now you should handle any cases where a user visits a page at an incorrect URL.</p>
                    <p>Such a case is called Error 404, which is slang for the situation where a web server should return the HTTP status code "404 Not Found" when a user visits a page that has been moved or deleted.</p>
                    <p>According to <a href="https://en.wikipedia.org/wiki/HTTP_404" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">Wikipedia</a>, the term "404 Not Found" was coined by Berners-Lee himself, who explained in a 1998 interview that he wanted to make the error message "slightly apologetic".</p>
                    <p>The first documented case of a 404 error appearing on a web page was in 1993, when a user tried to access a page about the Mosaic web browser on the NCSA website.</p>
                    <p>Since then, 404 errors have become one of the most common and recognizable errors on the Web. Many websites have customized their 404 pages with creative designs, messages, or features to entertain or assist their visitors. For example, Google's 404 page features a broken robot and a link to its homepage, while GitHub's 404 page shows a random image of a parallax star field and a link to its status page.</p>
                    <figure>
                        <img src="media/demo-posts/five.viewers/home-3.jpg" alt="">
                        <figcaption>
                            <a href="https://unsplash.com/photos/uFUQ55RuMrs" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">
                                Photo by Georgi Kalaydzhiev on Unsplash
                            </a>
                        </figcaption>
                    </figure>
                    <p>So, let's continue with the second viewer module called <b>Error404</b>.</p>
                </section>

                <section class="message hr">
                    <h1><b>Step 3</b> create an Error404 page</h1>
                    <p>Here you will also need to go through 3 substeps.</p>
                    <h4>Substep 3.1: Controller</h4>
                    <p>You should go back to the root folder of your app and create a subfolder <u>Error404</u> there, and in it a file <u>Error404.php</u> with the following contents.</p>
                    <pre data-title="Error404.php">&lt;?php
<ins>/** ------------------------------------------------------------------------
 * The viewer module for displaying a page at an incorrect URL. It is always
 * called using <em>run()</em> method implemented below. The initiator of that call
 * is the main module of your application.
 * ---------------------------------------------------------------------- */</ins>

    <em>mimimiInclude</em> ( '<u>Module.php</u>' );
    class <b>MyMimimiError404</b> extends <b>MimimiModule</b> {

        <ins>/** ----------------------------------------------------------------
         * Displays the contents of error page.
         * This method handles the following URLs:
         *     https://your.site/UNDEFINED-URL
         *                       └────┬──────┘
         *                            └─&gt; it is the incoming <i>$params</i>
         * @param  mixed <i>$params</i> The relative page URL as discovered by the app's routing method.
         * @return bool          <i>TRUE</i> if the page was rendered successfully.
         * -------------------------------------------------------------- */</ins>

        public function <em>run</em> ( <i>$params</i> = <strong>''</strong> ) {
            return <b>$this</b>-&gt;<b>app</b>-&gt;<em>renderTemplate</em> ( '<u>error-404.tpl</u>',
                                                <strong>'Oops, this site does not have a template for Error 404 case!'</strong> );
        }
    };</pre>
                    <h4>How does this work?</h4>
                    <ol class="cellular">
                        <li>Here, similar to handling the home page, the file with a base class <b>MimimiModule</b> is also loaded.</li>
                        <li>Then it is extended to the required class <b>MyMimimiError404</b>.</li>
                        <li>And then the desired algorithm for generating page content is written in its method <em>run()</em>.</li>
                    </ol>
                </section>
                <section class="message light">
                    <p>Please note that this method always returns TRUE if a template file <u>error-404.tpl</u> was rendered successfully.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 3.2: Model</h4>
                    <p>Similar to handling the home page, the <b>Error404</b> page also does not require you to query any database entries for it. Therefore, we do nothing at this substep.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 3.3: View</h4>
                    <p>Markup a template file for rendering HTML content of the <b>Error404</b> page. Go to the subfolder <u>Themes/default</u> and create a file <u>error-404.tpl</u> inside with the following contents.</p>
                    <pre data-title="error-404.tpl">&lt;?php
    <ins>/** --------------------------------------------------------------------
     * Send headers to the user's browser.
     * ------------------------------------------------------------------ */</ins>

    <em>sendHeaderHTML</em>    (   );
    <em>sendStatus404</em>     (   );
    <em>sendHeaderExpires</em> ( <i>0</i> );
    <em>stopIfHead</em>        (   );

    <ins>/** --------------------------------------------------------------------
     * Generate page content.
     * ------------------------------------------------------------------ */</ins>

?&gt;&lt;!DOCTYPE html&gt;
&lt;html lang="en-US" class="<i>error404-page</i>"&gt;
    &lt;head&gt;
        &lt;base href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;"&gt;
        &lt;meta charset="UTF-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
        &lt;meta name="robots"   content="noindex"&gt;
        &lt;title&gt;
            <strong>Page not found / Five Viewers demo</strong>
        &lt;/title&gt;
        &lt;link rel="stylesheet"    href="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>css/styles.css</u>"&gt;
        &lt;link rel="shortcut icon" href="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>images/favicon.ico</u>" type="image/ico"&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div class="<i>page</i>"&gt;
            &lt;?php <em>mimimiModule</em> ( '<u>snippets/header.tpl</u>' ) ?&gt;
            &lt;?php <em>mimimiModule</em> ( '<u>snippets/menu.tpl</u>'   ) ?&gt;

            &lt;main class="<i>content</i>"&gt;
                &lt;section class="<i>message modal</i>"&gt;
                    &lt;h1&gt;<strong>Page is not found!</strong>&lt;/h1&gt;
                    &lt;p&gt;<strong>Oops, something went wrong! There is no such page on this site.</strong>&lt;/p&gt;
                    &lt;nav class="<i>buttons</i>"&gt;
                        &lt;a class="<i>btn</i>" href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;"&gt;
                            <strong>Close</strong>
                        &lt;/a&gt;
                    &lt;/nav&gt;
                &lt;/section&gt;
            &lt;/main&gt;

            &lt;aside class="<i>hint</i>"&gt;
                <strong>... an HTML ...</strong>
            &lt;/aside&gt;

            &lt;?php <em>mimimiModule</em> ( '<u>snippets/footer.tpl</u>' ) ?&gt;
        &lt;/div&gt;
        &lt;script src="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>js/scripts.js</u>"&gt;&lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre>
                </section>

                <section class="message hr">
                    <h4>What does your build look like now?</h4>
                    <pre class="filelist">├─> [-] <strong>five.viewers</strong>
│       ├─> <strong class="newly">Error404</strong>
│       │   └─> <u class="newly">Error404.php</u>
│       │       <i class="newly">└─&lt;─ run()</i>
│       ├─> <strong>Home</strong>
│       │   └─> <u>Home.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Themes</strong>
│       │   └─> <strong>default</strong>
│       │       ├─> <strong>css</strong>
│       │       │   └─> <u>styles.css</u>
│       │       ├─> <strong>images</strong>
│       │       │   └─> <u>favicon.ico</u>
│       │       ├─> <strong>js</strong>
│       │       │   └─> <u>scripts.js</u>
│       │       │       <i>├─&lt;─ findTimes()</i>
│       │       │       <i>├─&lt;─ extractTime()</i>
│       │       │       <i>├─&lt;─ fixTimes()</i>
│       │       │       <i>├─&lt;─ displayRandom()</i>
│       │       │       <i>└─&lt;─ renderMessage()</i>
│       │       ├─> <strong>snippets</strong>
│       │       │   ├─> <u>footer.tpl</u>
│       │       │   ├─> <u>header.tpl</u>
│       │       │   └─> <u>menu.tpl</u>
│       │       ├─> <u class="newly">error-404.tpl</u>
│       │       └─> <u>home.tpl</u>
│       └─> <u>Application.php</u>
│           <i>├─&lt;─ property $viewerCollector</i>
│           <i>├─&lt;─ property $pollerCollector</i>
│           <i>├─&lt;─ property $systemCollector</i>
│           <i>├─&lt;─ property $allowedPollers</i>
│           <i>├─&lt;─ property $allowedViewers</i>
│           <i>└─&lt;─ property $myNodeFile</i>
├─> [+] <strong>media</strong>
├─> [+] <strong>mimimi.core</strong>
├─> [+] <strong>mimimi.install</strong>
├─> [+] <strong>mimimi.modules</strong>
├─> [+] <strong>newspaper</strong>
├─> [+] <strong>tiny.news.feed</strong>
├─> <u>.htaccess</u>
├─> <u>favicon.ico</u>
└─> <u>index.php</u></pre>
                    <p>A blinking green dot at the end of the file name indicates recently created folders or files. Green strings indicates functions or methods added to the file.</p>
                </section>

                <section class="message interlude hr">
                    <h2>Well, that's good work</h2>
                    <p>Let's think about the rules for crawling your site by search robots. To list such rules, there is a special file that is located in the site root. It is called <u>robots.txt</u></p>
                    <p>This is a regular text document, its content is static. However, you may need to dynamically generate a content. For example, to insert the actual hostname, the sitemap URL, and so on.</p>
                    <p>In this case, you will have to perform step 4 described below.</p>
                    <figure>
                        <img src="media/demo-posts/five.viewers/home-4.jpg" alt="">
                        <figcaption>
                            <a href="https://unsplash.com/photos/brown-and-gray-crab-on-white-sand-l4Xy2d8M-Xs" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">
                                Photo by Joshua Alan Davis on Unsplash
                            </a>
                        </figcaption>
                    </figure>
                    <p>In general, if you decide to use the <u>robots.txt</u> file on your site, it is recommended to consult with a SEO specialist or research the specification yourself, as an incorrectly assembled file can lead to negative consequences. In addition, search engines have their own requirements for assembling such a file.</p>
                    <nav class="buttons">
                        <a class="btn" href="https://datatracker.ietf.org/doc/html/rfc9309" rel="nofollow noopener noreferrer" target="_blank">
                            Read Specification
                        </a>
                    </nav>
                    <p>However, the presence or absence of <u>robots.txt</u> on the site is not an error. For example, sites with private access, web messengers, chats, forums, gaming platforms, CRM and other services do not have such a file at all. So if your site doesn't need <u>robots.txt</u> file, you can safely skip step 4.</p>
                </section>

                <section class="message hr">
                    <h1><b>Step 4</b> create a robots.txt document</h1>
                    <p>Here you will also need to go through 3 substeps.</p>
                    <h4>Substep 4.1: Controller</h4>
                    <p>Please go back to your app root. Create a subfolder <u>RobotsTxt</u> there. Create a file <u>RobotsTxt.php</u>, write the following in it.</p>
                    <pre data-title="RobotsTxt.php">&lt;?php
<ins>/** ------------------------------------------------------------------------
 * The viewer module for generating a <u>robots.txt</u> document. It is always
 * called using <em>run()</em> method implemented below. The initiator of that call
 * is the main module of your application.
 * ---------------------------------------------------------------------- */</ins>

    <em>mimimiInclude</em> ( '<u>Module.php</u>' );
    class <b>MyMimimiRobotsTxt</b> extends <b>MimimiModule</b> {

        <ins>/** ----------------------------------------------------------------
         * Generates the contents of ROBOTS document.
         * Only a single form of URL is processed by this method:
         *     https://your.site/robots.txt
         * Another forms are incorrect:
         *     https://your.site/robots.txt/UNDEFINED-OPTIONS
         *                                  └──────┬────────┘
         *                                         └─&gt; it is the incoming <i>$params</i>
         * @param  mixed <i>$params</i> The rest of page's URL if detected by the app's routing method.
         * @return bool          <i>TRUE</i>  if the document was rendered successfully.
         *                       <i>FALSE</i> if the page URL is incorrect and an Error404 page needs to be displayed.
         * -------------------------------------------------------------- */</ins>

        public function <em>run</em> ( <i>$params</i> = <strong>''</strong> ) {
            return <i>$params</i> == <strong>''</strong>
                &amp;&amp; <b>$this</b>-&gt;<b>app</b>-&gt;<em>renderTemplate</em> ( '<u>robots-txt.tpl</u>',
                                                <strong>'Oops, this site does not have a "robots.txt" template!'</strong> );
        }
    };</pre>
                </section>
                <section class="message light half">
                    <p>Please note that due to the specifics of the app's router, the <em>run()</em> method of the viewer module <b>RobotsTxt</b> is called for any root URLs or for any URLs starting with "robots.txt".</p>
                    <p>However, only a single form of URL is processed by this method:</p>
                    <ul>
                        <li><u title="https://your.site/robots.txt"></u></li>
                    </ul>
                </section>
                <section class="message light half">
                    <p>Another forms of the locator are incorrect and are therefore ignored, causing the app's router to display them later using the visual module <b>Error404</b>. This is what those incorrect forms look like:</p>
                    <ul>
                        <li><u title="https://your.site/robots.txt/UNDEFINED-OPTIONS"></u></li>
                    </ul>
                    <p>Ignoring is done using a directive <code><i>$params</i> == <strong>''</strong></code> above.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 4.2: Model</h4>
                    <p>Similar to handling the home page and Error 404 page, the <b>RobotsTxt</b> viewer module also does not require you to query any database entries for it. Therefore, we do nothing at this substep.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 4.3: View</h4>
                    <p>Markup a template file for rendering a content of the <b>RobotsTxt</b> document. Go to the subfolder <u>Themes/default</u> and create a file <u>robots-txt.tpl</u> inside with the following contents.</p>
                    <pre data-title="robots-txt.tpl">&lt;?php
    <em>sendHeaderTEXT</em> ( );
    <em>sendStatus200</em>  ( );
    <em>stopIfHead</em>     ( );

?&gt;User-agent: *
Sitemap: &lt;?php <em>printSiteUrl</em>   ( ) ?&gt;<u>sitemap</u>
Host:    &lt;?php <em>printDomainUrl</em> ( ) ?&gt;</pre>
                    <nav class="buttons">
                        <a class="btn" href="robots.txt" rel="nofollow" target="_blank">
                            See Result
                        </a>
                    </nav>
                </section>

                <section class="message hr">
                    <h4>What does your build look like now?</h4>
                    <pre class="filelist">├─> [-] <strong>five.viewers</strong>
│       ├─> <strong>Error404</strong>
│       │   └─> <u>Error404.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Home</strong>
│       │   └─> <u>Home.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong class="newly">RobotsTxt</strong>
│       │   └─> <u class="newly">RobotsTxt.php</u>
│       │       <i class="newly">└─&lt;─ run()</i>
│       ├─> <strong>Themes</strong>
│       │   └─> <strong>default</strong>
│       │       ├─> <strong>css</strong>
│       │       │   └─> <u>styles.css</u>
│       │       ├─> <strong>images</strong>
│       │       │   └─> <u>favicon.ico</u>
│       │       ├─> <strong>js</strong>
│       │       │   └─> <u>scripts.js</u>
│       │       │       <i>├─&lt;─ findTimes()</i>
│       │       │       <i>├─&lt;─ extractTime()</i>
│       │       │       <i>├─&lt;─ fixTimes()</i>
│       │       │       <i>├─&lt;─ displayRandom()</i>
│       │       │       <i>└─&lt;─ renderMessage()</i>
│       │       ├─> <strong>snippets</strong>
│       │       │   ├─> <u>footer.tpl</u>
│       │       │   ├─> <u>header.tpl</u>
│       │       │   └─> <u>menu.tpl</u>
│       │       ├─> <u>error-404.tpl</u>
│       │       ├─> <u>home.tpl</u>
│       │       └─> <u class="newly">robots-txt.tpl</u>
│       └─> <u>Application.php</u>
│           <i>├─&lt;─ property $viewerCollector</i>
│           <i>├─&lt;─ property $pollerCollector</i>
│           <i>├─&lt;─ property $systemCollector</i>
│           <i>├─&lt;─ property $allowedPollers</i>
│           <i>├─&lt;─ property $allowedViewers</i>
│           <i>└─&lt;─ property $myNodeFile</i>
├─> [+] <strong>media</strong>
├─> [+] <strong>mimimi.core</strong>
├─> [+] <strong>mimimi.install</strong>
├─> [+] <strong>mimimi.modules</strong>
├─> [+] <strong>newspaper</strong>
├─> [+] <strong>tiny.news.feed</strong>
├─> <u>.htaccess</u>
├─> <u>favicon.ico</u>
└─> <u>index.php</u></pre>
                    <p>A blinking green dot at the end of the file name indicates recently created folders or files. Green strings indicates functions or methods added to the file.</p>
                </section>

                <section class="message interlude hr">
                    <h2>What about the sitemap?</h2>
                    <p>You may have noticed that the template file <u>robots-txt.tpl</u> had a reference to a sitemap. So you will need to create a viewer module with the same name.</p>
                    <figure>
                        <img src="media/demo-posts/five.viewers/home-5.jpg" alt="">
                        <figcaption>
                            <a href="https://unsplash.com/photos/site-plan-paper-0bzYFCQcljg" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">
                                Photo by Marjan Blan on Unsplash
                            </a>
                        </figcaption>
                    </figure>
                    <p>Sitemap is a general name for XML documents containing information about web pages that are subject to indexing.</p>
                    <p>The information provided in these documents may have varying degrees of completeness. In the simplest case, it contains only the URLs of pages to be indexed, as shown in the following example.</p>
                    <pre>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"&gt;
    &lt;url&gt;
        &lt;loc&gt; https://your.site/channels/demo-channel-3 &lt;/loc&gt;
    &lt;/url&gt;
    &lt;url&gt;
        &lt;loc&gt; https://your.site/channels/demo-channel-4 &lt;/loc&gt;
    &lt;/url&gt;
    &lt;url&gt;
        &lt;loc&gt; https://your.site/channels/demo-channel-2 &lt;/loc&gt;
    &lt;/url&gt;
    &lt;url&gt;
        &lt;loc&gt; https://your.site/channels/demo-channel-1 &lt;/loc&gt;
    &lt;/url&gt;
&lt;/urlset&gt;</pre>
                    <p>Using a sitemap does not guarantee that web pages will be indexed by search engines. This XML document only tells crawlers which URLs should be crawled more thoroughly.</p>
                </section>

                <section class="message hr">
                    <h1><b>Step 5</b> create a sitemap XML document</h1>
                    <h4>Substep 5.1: Controller</h4>
                    <p>You need to go back to your app root again. Create a subfolder <u>Sitemap</u> there. Create a file <u>Sitemap.php</u>, write the following in it.</p>
                    <pre data-title="Sitemap.php">&lt;?php
<ins>/** ------------------------------------------------------------------------
 * The viewer module for generating a sitemap XML document. It is always
 * called using <em>run()</em> method implemented below. The initiator of that call
 * is the main module of your application.
 * ---------------------------------------------------------------------- */</ins>

    <em>mimimiInclude</em> ( '<u>Module.php</u>' );
    class <b>MyMimimiSitemap</b> extends <b>MimimiModule</b> {

        <ins>/** ----------------------------------------------------------------
         * Generates the contents of SITEMAP document.
         * Only a single form of URL is processed by this method:
         *     https://your.site/sitemap
         * Another forms are incorrect:
         *     https://your.site/sitemap/UNDEFINED-OPTIONS
         *                               └──────┬────────┘
         *                                      └─&gt; it is the incoming <i>$params</i>
         * @param  mixed <i>$params</i> The rest of page's URL if detected by the app's routing method.
         * @return bool          <i>TRUE</i>  if the document was rendered successfully.
         *                       <i>FALSE</i> if the page URL is incorrect and an Error404 page needs to be displayed.
         * -------------------------------------------------------------- */</ins>

        public function <em>run</em> ( <i>$params</i> = <strong>''</strong> ) {
            return   <i>$params</i> == <strong>''</strong>
                &amp;&amp; ( <i>$data</i> = <b>$this</b>-&gt;<b>app</b>-&gt;<b>channels</b>-&gt;<em>getSitemap</em> ( ) )
                &amp;&amp;           <b>$this</b>-&gt;<b>app</b>-&gt;<em>renderTemplate</em>       ( '<u>sitemap.tpl</u>',
                                                                <strong>'Oops, this site does not have a sitemap template!'</strong>,
                                                                <i>$data</i> );
        }
    };</pre>
                </section>
                <section class="message light half">
                    <p>Please note that due to the specifics of the app's router, the <em>run()</em> method of the viewer module <b>Sitemap</b> is called for any root URLs or for any URLs starting with "sitemap".</p>
                    <p>However, only a single form of URL is processed by this method:</p>
                    <ul>
                        <li><u title="https://your.site/sitemap"></u></li>
                    </ul>
                </section>
                <section class="message light half">
                    <p>Another forms of the locator are incorrect and are therefore ignored, causing the app's router to display them later using the visual module <b>Error404</b>. This is what those incorrect forms look like:</p>
                    <ul>
                        <li><u title="https://your.site/sitemap/UNDEFINED-OPTIONS"></u></li>
                    </ul>
                    <p>Ignoring is done using a directive <code><i>$params</i> == <strong>''</strong></code> above.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 5.2: Model</h4>
                    <p>Please note that this viewer module requires you to query the database for entries. They are sitemap URLs and are represented as follows.</p>
                    <pre>Array (
    [0] =&gt; Array (
            [ <strong>'url'</strong>    ] =&gt; 'demo-channel-3',
            [ <strong>'viewer'</strong> ] =&gt; 'channels/'
        ),
    [1] =&gt; Array (
            [ <strong>'url'</strong>    ] =&gt; 'demo-channel-4',
            [ <strong>'viewer'</strong> ] =&gt; 'channels/'
        ),
    [2] =&gt; Array (
            [ <strong>'url'</strong>    ] =&gt; 'demo-channel-2',
            [ <strong>'viewer'</strong> ] =&gt; 'channels/'
        ),
    [3] =&gt; Array (
            [ <strong>'url'</strong>    ] =&gt; 'demo-channel-1',
            [ <strong>'viewer'</strong> ] =&gt; 'channels/'
        )
)</pre>

                    <p>To query these entries we use a directive <code>$this-&gt;app-&gt;channels-&gt;<em>getSitemap()</em></code> above and the following method which will be written later when creating the <b>Channels</b> viewer module (please see substep 7.1).</p>
                    <pre data-title="fragment of Channels.php">&lt;?php
    ...
        <ins>/** ----------------------------------------------------------------
         * Retrieves all channels entries for a sitemap.
         * These entries are sorted by their creation date. Please note that
         * <strong>t1</strong> below is an alias for the primary database table <b>channels</b>.
         * @return array|bool <i>ARRAY</i> on success. Each element is an array that contains the <strong>url</strong> and <strong>viewer</strong> columns only.
         *                    <i>FALSE</i> on failure. This means no entries were found.
         * -------------------------------------------------------------- */</ins>

        public function <em>getSitemap</em> ( ) {
            <i>$filter</i> = [
                <strong>'select'</strong>  => [ <strong>'t1.url'</strong>      => <i>TRUE</i>     ,
                               '<strong>"channels/"</strong>' => '<i>viewer</i>' ],
                <ins>/* where */</ins>    <strong>'t1.enabled'</strong>  =&gt; <i>TRUE</i>,
                <strong>'orderby'</strong> =&gt; [ <strong>'t1.created'</strong>  =&gt; <i>'desc'</i> ]
            ];
            return <b>$this</b>-&gt;<em>select</em> ( <i>$filter</i>, 0, <i>1000000000</i> );
        }
    ...</pre>
                    <p>The filter used above will actually result in the following MySQL query:</p>
                    <pre>SELECT   <strong>t1.url</strong>,
         <strong>"channels/"</strong> AS <i>viewer</i>
FROM     <b>channels</b> AS <strong>t1</strong>
WHERE    <strong>t1.enabled</strong> = <i>TRUE</i>
ORDER BY <strong>t1.created</strong> <i>DESC</i>
LIMIT    <i>1000000000</i></pre>
                    <p>Note that the <b>channels</b> is a database table associated with the <b>Channels</b> viewer module.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 5.3: View</h4>
                    <p>Markup a template file for rendering a content of the <b>Sitemap</b> document. Go to the subfolder <u>Themes/default</u> and create a file <u>sitemap.tpl</u> inside with the following contents.</p>
                    <pre data-title="sitemap.tpl">&lt;?php
    <em>sendHeaderXML</em> ( );
    <em>sendStatus200</em> ( );
    <em>stopIfHead</em>    ( );

?&gt;&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"&gt;
    &lt;?php
        if ( <i>$params</i> ) {
            foreach ( <i>$params</i> as <i>$entry</i> ) {
                ?&gt;
                &lt;url&gt;
                    &lt;loc&gt;
                        &lt;?php
                            <em>printSiteUrl</em> (                    );
                            <em>printValue</em>   ( <i>$entry</i>[ <strong>'viewer'</strong> ] );
                            <em>printValue</em>   ( <i>$entry</i>[ <strong>'url'</strong>    ] );
                        ?&gt;
                    &lt;/loc&gt;
                &lt;/url&gt;
                &lt;?php
            }
        }
    ?&gt;
&lt;/urlset&gt;</pre>
                    <p>Note that each template has an incoming variable <i>$params</i> through which the associated module or other caller can pass data collected specifically for that template.</p>
                    <nav class="buttons">
                        <a class="btn" href="sitemap" rel="nofollow" target="_blank">
                            See Result
                        </a>
                    </nav>
                </section>

                <section class="message hr">
                    <h4>What does your build look like now?</h4>
                    <pre class="filelist">├─> [-] <strong>five.viewers</strong>
│       ├─> <strong>Error404</strong>
│       │   └─> <u>Error404.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Home</strong>
│       │   └─> <u>Home.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>RobotsTxt</strong>
│       │   └─> <u>RobotsTxt.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong class="newly">Sitemap</strong>
│       │   └─> <u class="newly">Sitemap.php</u>
│       │       <i class="newly">└─&lt;─ run()</i>
│       ├─> <strong>Themes</strong>
│       │   └─> <strong>default</strong>
│       │       ├─> <strong>css</strong>
│       │       │   └─> <u>styles.css</u>
│       │       ├─> <strong>images</strong>
│       │       │   └─> <u>favicon.ico</u>
│       │       ├─> <strong>js</strong>
│       │       │   └─> <u>scripts.js</u>
│       │       │       <i>├─&lt;─ findTimes()</i>
│       │       │       <i>├─&lt;─ extractTime()</i>
│       │       │       <i>├─&lt;─ fixTimes()</i>
│       │       │       <i>├─&lt;─ displayRandom()</i>
│       │       │       <i>└─&lt;─ renderMessage()</i>
│       │       ├─> <strong>snippets</strong>
│       │       │   ├─> <u>footer.tpl</u>
│       │       │   ├─> <u>header.tpl</u>
│       │       │   └─> <u>menu.tpl</u>
│       │       ├─> <u>error-404.tpl</u>
│       │       ├─> <u>home.tpl</u>
│       │       ├─> <u>robots-txt.tpl</u>
│       │       └─> <u class="newly">sitemap.tpl</u>
│       └─> <u>Application.php</u>
│           <i>├─&lt;─ property $viewerCollector</i>
│           <i>├─&lt;─ property $pollerCollector</i>
│           <i>├─&lt;─ property $systemCollector</i>
│           <i>├─&lt;─ property $allowedPollers</i>
│           <i>├─&lt;─ property $allowedViewers</i>
│           <i>└─&lt;─ property $myNodeFile</i>
├─> [+] <strong>media</strong>
├─> [+] <strong>mimimi.core</strong>
├─> [+] <strong>mimimi.install</strong>
├─> [+] <strong>mimimi.modules</strong>
├─> [+] <strong>newspaper</strong>
├─> [+] <strong>tiny.news.feed</strong>
├─> <u>.htaccess</u>
├─> <u>favicon.ico</u>
└─> <u>index.php</u></pre>
                    <p>A blinking green dot at the end of the file name indicates recently created folders or files. Green strings indicates functions or methods added to the file.</p>
                </section>

                <section class="message interlude hr">
                    <h2>It's time to talk about API</h2>
                    <p>According to the task described at the beginning of this guide, one page of your site should serve as a kind of API.</p>
                    <p>Currently this API will only handle one function named <u>random</u>. And periodic calling of that function will be organized using polling.</p>
                    <h3>What is a polling?</h3>
                    <p>Web polling is the process where a site waits for its backend to dynamically read the requested data or report its status. It can be implemented in four ways: regular polling, long polling, polling by WebSocket, and polling by Server Sent Events.</p>
                    <h4>Regular polling</h4>
                    <p>It is a short connection to request the required data and a long pause to wait for the next time.</p>
                    <p>This method is effective with low-cost hosting when a small number of processor minutes are allocated. Typically it is implemented in JavaScript as follows:</p>
                    <pre>let busyNow   = false,
    url       = '',
    urlParams = { },
    longPause = <i>20000</i>;

async function <em>regularPolling</em> ( ) {
    if ( ! busyNow ) {
        if ( url ) {
            busyNow = true;
            const params   = buildParams ( ),
                  response = await fetch ( url, params );
            switch ( response.status ) {
                case 200:
                     const data = await response.json ( );
                     <b>handlePackage</b> ( data );
                     break;
                default:
                     <ins>/* console.log ( 'Error happens: ' + response.statusText ); */</ins>
            }
            busyNow = false;
        }
    }
};

setInterval ( <em>regularPolling</em>, longPause );

function buildParams ( ) {
    const data = new FormData ( );
          for ( const key in urlParams ) {
              data.append ( key, urlParams[ key ] );
          }
    return {
        method: 'POST',
        body:   data,
        mode:   'cors',
        cache:  'no-cache'
    };
};

function <b>handlePackage</b> ( data ) {
    stopPolling ( );
    <ins>/* you need to parse the package content here   */
    /* and set the appropriate value of the url and */
    /* urlParams variable if you need to perform    */
    /* another request                              */</ins>
};

function stopPolling ( ) {
    url       = '';
    urlParams = { };
};</pre>
                    <p>Automatic launch occurs as follows within the next <i>longPause</i> microseconds:</p>
                    <pre>url       =   <strong>'some-checkpoint-url'</strong>;
urlParams = { <i>'some'</i>: <i>'parameters'</i>,
              <i>'for'</i> : <i>'that checkpoint'</i> };</pre>
                    <h4>Long polling</h4>
                    <p>It is a long connection to wait for the required data and a short pause to reestablish that connection.</p>
                    <p>This method is similar to regular polling, but consumes more server resources and CPU minutes due to the continuous flow of requests. Therefore, it is not recommended to use it on low-cost hosting.</p>
                    <p>Typically it is implemented in JavaScript as follows:</p>
                    <pre>let url        = '',
    urlParams  = { },
    shortPause = <i>50</i>;

async function <em>longPolling</em> ( ) {
    if ( url ) {
        const params   = = buildParams ( ),
              response = await fetch ( url, params );
        switch ( response.status ) {
            case 200:
                 const data = await response.json ( );
                 <b>handlePackage</b> ( data );
                 break;
            case 502:
                 <ins>/* console.log ( 'Connection timeout happens' ); */</ins>
                 break;
            default:
                 <ins>/* console.log ( 'Another error happens: ' + response.statusText ); */</ins>
        }
    }
    await new Promise (
              ( resolve ) =&gt; setTimeout ( resolve, shortPause )
          );
    <em>longPolling</em> ( );
};

<em>longPolling</em> ( );

function buildParams ( ) {
    const data = new FormData ( );
          for ( const key in urlParams ) {
              data.append ( key, urlParams[ key ] );
          }
    return {
        method: 'POST',
        body:   data,
        mode:   'cors',
        cache:  'no-cache'
    };
};

function <b>handlePackage</b> ( data ) {
    stopPolling ( );
    <ins>/* you need to parse the package content here   */
    /* and set the appropriate value of the url and */
    /* urlParams variable if you need to perform    */
    /* another request                              */</ins>
};

function stopPolling ( ) {
    url       = '';
    urlParams = { };
};</pre>
                    <p>Automatic launch occurs as follows within the next <i>shortPause</i> microseconds:</p>
                    <pre>url       =   <strong>'some-checkpoint-url'</strong>;
urlParams = { <i>'some'</i>: <i>'parameters'</i>,
              <i>'for'</i> : <i>'that checkpoint'</i> };</pre>
                    <h4>WebSocket polling</h4>
                    <p>It is a persistent TCP connection for requesting required data over a full-duplex communication channel in real time.</p>
                    <p>However, this method requires that your hosting supports WebSocket protocol. Typically it is implemented in JavaScript as follows:</p>
                    <pre>let socket = null;

function <em>websocketPolling</em> ( url, params = null ) {
    socket = new WebSocket ( 'wss://your.site/' + url );
    socket.onopen = function ( event ) {
        socket.onmessage = function ( event ) {
            <b>handlePackage</b> ( event.data );
        };
        if ( params ) socket.send ( params );
    };
    socket.onerror = function ( event ) {
        stopPolling ( );
    };
};

<em>websocketPolling</em> ( <strong>'some-checkpoint-url'</strong> );

function <b>handlePackage</b> ( data ) {
    <ins>/* you need to parse the package content here */</ins>
};

function stopPolling ( ) {
    if ( socket ) socket.close ( );
};</pre>
                    <h4>Server Sent Events polling</h4>
                    <p>It is a pseudo persistent HTTP connection for reading required data over an unidirectional communication channel in real time.</p>
                    <p>Typically it is implemented in JavaScript as follows:</p>
                    <pre>let source = null;

function <em>ssePolling</em> ( url ) {
    if ( typeof EventSource !== 'undefined' ) {
        source = new EventSource ( url );
        socket.onopen = function ( event ) {
            source.onmessage = function ( event ) {
                <b>handlePackage</b> ( event.data );
            };
        };
        source.onerror = function ( event ) {
            if ( this.readyState == EventSource.CONNECTING ) {
                <em>ssePolling</em> ( url );
            } else {
                stopPolling ( );
            }
        };
    }
};

<em>ssePolling</em> ( <strong>'some-checkpoint-url'</strong> );

function <b>handlePackage</b> ( data ) {
    <ins>/* you need to parse the package content here */</ins>
};

function stopPolling ( ) {
    if ( source ) source.close ( );
};</pre>
                    <h4>Okay</h4>
                    <p>So let's now create a viewer module called <b>Api</b>. It will even support different versions.</p>
                    <p>By the way, its URL format will look like this:</p>
                    <ul>
                        <li><u title="https://your.site/polls/api/VERSION/COMMAND/OPTIONS"></u></li>
                    </ul>
                    <p>This format will allow API requests to be accurately routed according to the requested API version, the desired command to execute and its execution parameters.</p>
                    <figure>
                        <img src="media/demo-posts/five.viewers/home-6.jpg" alt="">
                        <figcaption>
                            <a href="https://unsplash.com/photos/brown-wooden-signage-under-blue-sky-during-daytime-xwl82KMGgFo" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">
                                Photo by Burkhard Kaufhold on Unsplash
                            </a>
                        </figcaption>
                    </figure>
                    <p>And for now, the only version will be <strong>v1</strong>. And the only command will be <strong>random</strong> with an optional parameter <i>from</i>.</p>
                </section>

                <section class="message hr">
                    <h1><b>Step 6</b> create a checkpoint</h1>
                    <h4>Substep 6.1: Controller</h4>
                    <p>Please go back to your app root. Create a subfolder <u>Api</u> there. Create a file <u>Api.php</u>, write the following in it.</p>
                    <pre data-title="Api.php">&lt;?php
<ins>/** ------------------------------------------------------------------------
 * The poller module for requesting data via the API. It is always called
 * using <em>run()</em> method implemented below. The initiator of that call is the
 * main module of your application.
 * ---------------------------------------------------------------------- */</ins>

    <em>mimimiInclude</em> ( '<u>Module.php</u>' );
    class <b>MyMimimiApi</b> extends <b>MimimiModule</b> {

        <ins>/** ----------------------------------------------------------------
         * Sends a result of the current request.
         * Currently only two forms of URL are processed by this method:
         *     https://your.site/polls/api/v1/random
         *     https://your.site/polls/api/v1/random/from/CHANNEL-ID
         *                                 └──────────┬────────────┘
         *                                            └─&gt; it is the incoming <i>$params</i>
         * @param  string <i>$params</i> The rest of page's URL if detected by the app's routing method.
         * @return bool           <i>TRUE</i>  if the result was sent successfully.
         *                        <i>FALSE</i> if the page URL is incorrect and an Error404 page needs to be displayed.
         * -------------------------------------------------------------- */</ins>

        public function <em>run</em> ( <i>$params</i> = <strong>''</strong> ) {
            <i>$version</i> = <b>$this</b>-&gt;<b>app</b>-&gt;<em>cutUrlSegment</em> ( <i>$params</i> );
            switch ( <i>$version</i> ) {
                case <strong>'v1'</strong>: return <b>$this</b>-&gt;<em>routeVersion1</em> ( <i>$params</i> );
            }
            return <i>FALSE</i>;
        }

        <ins>/** ----------------------------------------------------------------
         * Processes a request for API version 1.
         * This method parses the URL for a matching command, and if it
         * finds one, sends the rest of the URL to the appropriate handler.
         * In this example, the following URLs correspond to these commands:
         *     https://your.site/polls/api/v1/random
         *     https://your.site/polls/api/v1/random/REST-OF-URL
         *                                    └───────┬────────┘
         *                                            └─&gt; it is the incoming <i>$url</i>
         * @param  string <i>$url</i> The rest of page's URL containing a command and its options.
         * @return bool        <i>TRUE</i>  if the result was sent successfully.
         *                     <i>FALSE</i> if the command is incorrect and an Error404 page needs to be displayed.
         * -------------------------------------------------------------- */</ins>

        protected function <em>routeVersion1</em> ( <i>$url</i> ) {
            <i>$command</i> = <b>$this</b>-&gt;<b>app</b>-&gt;<em>cutUrlSegment</em> ( <i>$url</i> );
            switch ( <i>$command</i> ) {
                case <strong>'random'</strong>: return <b>$this</b>-&gt;<em>doRandomVersion1</em> ( <i>$url</i> );
            }
            return <i>FALSE</i>;
        }

        <ins>/** ----------------------------------------------------------------
         * Executes the "random" command for API version 1.
         * This method handles the following URLs:
         *     https://your.site/polls/api/v1/random
         *     https://your.site/polls/api/v1/random/from/CHANNEL-ID
         *                                           └──────┬──────┘
         *                                                  └─&gt; it is the incoming <i>$options</i>
         * @param  string <i>$options</i> The rest of page's URL containing the command options.
         * @return bool            <i>TRUE</i>  if the result was sent successfully.
         *                         <i>FALSE</i> if the options are incorrect and an Error404 page needs to be displayed.
         * -------------------------------------------------------------- */</ins>

        protected function <em>doRandomVersion1</em> ( <i>$options</i> ) {
            <i>$target</i> = <b>$this</b>-&gt;<b>app</b>-&gt;<em>cutUrlSegment</em> ( <i>$options</i> );
            switch ( <i>$target</i> ) {
                case <strong>'from'</strong>:
                     if ( <i>$options</i> == <strong>''</strong> ) break;
                case <strong>''</strong>:
                     <b>$this</b>-&gt;<em>sendJsonHeaders</em> (        );
                              <del>$this-&gt;app-&gt;channels-&gt;install ( );</del>
                     <i>$entry</i> = <b>$this</b>-&gt;<b>app</b>-&gt;<b>channels</b>-&gt;<b>messages</b>-&gt;<em>getRandom</em> ( <i>$options</i> );
                     <b>$this</b>-&gt;<em>sendJsonData</em>    ( <i>$entry</i> );
                     return <i>TRUE</i>;
            }
            return <i>FALSE</i>;
        }

        <ins>/** ----------------------------------------------------------------
         * Sends JSON headers.
         * -------------------------------------------------------------- */</ins>

        protected function <em>sendJsonHeaders</em> ( ) {
            <b>$this</b>-&gt;<b>app</b>-&gt;<em>runHelper</em> (   );
            <em>sendHeaderJSON</em>        (   );
            <em>sendStatus200</em>         (   );
            <em>sendHeaderExpires</em>     ( <i>0</i> );
            <em>stopIfHead</em>            (   );
        }

        <ins>/** ----------------------------------------------------------------
         * Sends a JSON response.
         * @param array <i>$data</i> Items to be sent to the browser.
         * -------------------------------------------------------------- */</ins>

        protected function <em>sendJsonData</em> ( <i>$data</i> ) {
            echo <i>$data</i>
                 ? json_encode ( <i>$data</i> )
                 : <strong>'{}'</strong>;
        }
    };</pre>
                </section>
                <section class="message light">
                    <p>Strikethrough indicates lines that are written for demonstration purposes, they may be removed on a production site.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 6.2: Model</h4>
                    <p>This viewer module requires you to query the database for an entry. It is represented as follows.</p>
                    <pre>Array (
    [ <strong>'name'</strong>    ] =&gt; 'Julius Caesar',
    [ <strong>'title'</strong>   ] =&gt; 'Quote, Act III, Scene II',
    [ <strong>'image'</strong>   ] =&gt; '',
    [ <strong>'text'</strong>    ] =&gt; '&lt;p&gt;As he was valiant, I honour him; but, as he was ambitious, I slew him&lt;/p&gt;',
    [ <strong>'created'</strong> ] =&gt; '2024-03-14 11:35:45'
)</pre>
                    <p>To query this entry we use a directive <code>$this-&gt;app-&gt;channels-&gt;messages-&gt;<em>getRandom</em>(<i>$options</i>);</code> above and the following method which will be written later when creating the <b>Messages</b> submodule (please see substep 7.3).</p>
                    <pre data-title="fragment of Messages.php">&lt;?php
    ...
        <ins>/** ----------------------------------------------------------------
         * Retrieves a random entry with visible status.
         * Please note that <strong>t1</strong> below is an alias for the secondary database
         * table <b>channels_messages</b>. And <strong>t2</strong> is an alias for the primary
         * table <b>channels</b>, which is referenced through <b>$this</b>-&gt;<b>owner</b>.
         * @param  int        <i>$id</i> The channel identifier to search in.
         * @return array|bool     <i>ARRAY</i> on success. It contains an entry obtained from a database table.
         *                        <i>FALSE</i> on failure. This means no entries were found.
         * -------------------------------------------------------------- */</ins>

        public function <em>getRandom</em> ( <i>$id</i> = 0 ) {
            <i>$count</i> = <b>$this</b>-&gt;<em>getCount</em> ( <i>$id</i> );
            if ( <i>$count</i> ) {
                <i>$filter</i> = [
                    <strong>'select'</strong> =&gt; [ <strong>'t2.name'</strong>    =&gt; <i>TRUE</i> ,
                                  <strong>'t1.title'</strong>   =&gt; <i>TRUE</i> ,
                                  <strong>'t1.image'</strong>   =&gt; <i>TRUE</i> ,
                                  <strong>'t1.text'</strong>    =&gt; <i>TRUE</i> ,
                                  <strong>'t1.created'</strong> =&gt; <i>TRUE</i> ],
                    <strong>'join'</strong>   =&gt; [ <b>$this</b>-&gt;<b>owner</b>-&gt;<i>table</i> =&gt; [
                                                <strong>'t1.channel_id'</strong> =&gt; <strong>'t2.id'</strong> ] ],
                    <ins>/* where */</ins>   <strong>'t1.visible'</strong> =&gt; <i>TRUE</i>,
                                  <strong>'t2.enabled'</strong> =&gt; <i>TRUE</i>
                ];
                if ( <i>$id</i> ) {
                    <ins>/* where */</ins>   <i>$filter</i>[ <strong>'t1.channel_id'</strong> ] = <i>$id</i>;
                }
                <i>$offset</i> = <i>$count</i> &gt; 1
                                 ? mt_rand ( 0, <i>$count</i> - 1 )
                                 : 0;
                return <b>$this</b>-&gt;<em>select</em> ( <i>$filter</i>, <i>$offset</i> );
            }
            return <i>FALSE</i>;
        }
    ...</pre>
                    <p>The filter used above will actually result in the following MySQL query:</p>
                    <pre>SELECT    <strong>t2.name</strong>,
          <strong>t1.title</strong>,
          <strong>t1.image</strong>,
          <strong>t1.text</strong>,
          <strong>t1.created</strong>
FROM      <b>channels_messages</b> AS <strong>t1</strong>
LEFT JOIN <b>channels</b>          AS <strong>t2</strong>
                               ON <strong>t1.channel_id</strong> = <strong>t2.id</strong>
WHERE     <strong>t1.visible</strong>    = <i>TRUE</i> AND
          <strong>t2.enabled</strong>    = <i>TRUE</i> AND
          <strong>t1.channel_id</strong> = <i>$id</i>
LIMIT     1
OFFSET    <i>$offset</i></pre>
                    <p>Note that the <b>channels</b> is a database table associated with the <b>Channels</b> viewer module, and its submodule <b>Messages</b> has an associated table <b>channels_messages</b>.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 6.3: View</h4>
                    <p>This viewer module does not have a specific template file for generating the response. Instead, it uses the <em>sendJsonHeaders()</em> and <em>sendJsonData()</em> methods implemented above.</p>
                    <nav class="buttons">
                        <a class="btn" href="polls/api/v1/random" rel="nofollow" target="_blank">
                            See Response
                        </a>
                    </nav>
                    <p>Or you can see below how a similar response is displayed every 15 seconds. Here, a regular polling on Javascript was used to display a random quote from William Shakespeare's play Julius Caesar (it is entry 2 in the demo database).</p>
                </section>

                <section class="message idle"
                         data-poller="api"
                         data-version="v1"
                         data-command="random"
                         data-options="from/2"
                         data-timer="15"
                         data-success="displayRandom"
                         data-title="Live Demo of that Response"></section>

                <section class="message hr">
                    <h4>What does your build look like now?</h4>
                    <pre class="filelist">├─> [-] <strong>five.viewers</strong>
│       ├─> <strong class="newly">Api</strong>
│       │   └─> <u class="newly">Api.php</u>
│       │       <i class="newly">├─&lt;─ run()</i>
│       │       <i class="newly">├─&lt;─ routeVersion1()</i>
│       │       <i class="newly">├─&lt;─ doRandomVersion1()</i>
│       │       <i class="newly">├─&lt;─ sendJsonHeaders()</i>
│       │       <i class="newly">└─&lt;─ sendJsonData()</i>
│       ├─> <strong>Error404</strong>
│       │   └─> <u>Error404.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Home</strong>
│       │   └─> <u>Home.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>RobotsTxt</strong>
│       │   └─> <u>RobotsTxt.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Sitemap</strong>
│       │   └─> <u>Sitemap.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Themes</strong>
│       │   └─> <strong>default</strong>
│       │       ├─> <strong>css</strong>
│       │       │   └─> <u>styles.css</u>
│       │       ├─> <strong>images</strong>
│       │       │   └─> <u>favicon.ico</u>
│       │       ├─> <strong>js</strong>
│       │       │   └─> <u>scripts.js</u>
│       │       │       <i>├─&lt;─ findTimes()</i>
│       │       │       <i>├─&lt;─ extractTime()</i>
│       │       │       <i>├─&lt;─ fixTimes()</i>
│       │       │       <i>├─&lt;─ displayRandom()</i>
│       │       │       <i>├─&lt;─ renderMessage()</i>
│       │       │       <i class="newly">├─&lt;─ findPollers()</i>
│       │       │       <i class="newly">├─&lt;─ startPollers()</i>
│       │       │       <i class="newly">├─&lt;─ pollingFor()</i>
│       │       │       <i class="newly">├─&lt;─ getPollerUrl()</i>
│       │       │       <i class="newly">├─&lt;─ getPollerOptions()</i>
│       │       │       <i class="newly">├─&lt;─ getPollerTime()</i>
│       │       │       <i class="newly">└─&lt;─ loadDocument()</i>
│       │       ├─> <strong>snippets</strong>
│       │       │   ├─> <u>footer.tpl</u>
│       │       │   ├─> <u>header.tpl</u>
│       │       │   └─> <u>menu.tpl</u>
│       │       ├─> <u>error-404.tpl</u>
│       │       ├─> <u>home.tpl</u>
│       │       ├─> <u>robots-txt.tpl</u>
│       │       └─> <u>sitemap.tpl</u>
│       └─> <u>Application.php</u>
│           <i>├─&lt;─ property $viewerCollector</i>
│           <i>├─&lt;─ property $pollerCollector</i>
│           <i>├─&lt;─ property $systemCollector</i>
│           <i>├─&lt;─ property $allowedPollers</i>
│           <i>├─&lt;─ property $allowedViewers</i>
│           <i>└─&lt;─ property $myNodeFile</i>
├─> [+] <strong>media</strong>
├─> [+] <strong>mimimi.core</strong>
├─> [+] <strong>mimimi.install</strong>
├─> [+] <strong>mimimi.modules</strong>
├─> [+] <strong>newspaper</strong>
├─> [+] <strong>tiny.news.feed</strong>
├─> <u>.htaccess</u>
├─> <u>favicon.ico</u>
└─> <u>index.php</u></pre>
                    <p>A blinking green dot at the end of the file name indicates recently created folders or files. Green strings indicates functions or methods added to the file.</p>
                </section>

                <section class="message interlude hr">
                    <figure>
                        <img src="media/demo-posts/five.viewers/home-7.jpg" alt="">
                        <figcaption>
                            <a href="https://unsplash.com/photos/a-group-of-people-standing-around-a-display-of-video-screens-IayKLkmz6g0" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">
                                Photo by Maxim Hopman on Unsplash
                            </a>
                        </figcaption>
                    </figure>
                </section>

                <section class="message hr">
                    <h1><b>Step 7</b> create a channel list and own page</h1>
                    <p>Since this viewer module serves two types of pages, you will need to go through 5 substeps here:</p>
                    <ol class="cellular">
                        <li>Controller.</li>
                        <li>Model 1 to work with the primary database table.</li>
                        <li>Subordinate Model 2 to work with the secondary table.</li>
                        <li>View 1 to display a channel list.</li>
                        <li>View 2 to display the channel's own page.</li>
                    </ol>
                </section>

                <section class="message hr">
                    <h4>Substep 7.1: Controller</h4>
                    <p>You should go back to the root folder of your app and create a subfolder <u>Channels</u> there, and in it a file <u>Channels.php</u> with the following contents.</p>
                    <pre data-title="Channels.php">&lt;?php
<ins>/** ------------------------------------------------------------------------
 * The viewer module for displaying a channel list or channel page. It is
 * always called using <em>run()</em> method implemented below. The initiator of that
 * call is the main module of your application.
 *
 * We can say that it is a Controller. However, this module also acts as
 * a Model to read certain data from the database. So it contains the
 * corresponding methods: <em>getItem()</em>, <em>getItems()</em>, and <em>getSitemap()</em>.
 * ---------------------------------------------------------------------- */</ins>

    <em>mimimiInclude</em> ( '<u>NodeWithTable.php</u>' );
    class <b>MyMimimiChannels</b> extends <b>MimimiNodeModuleWithTable</b> {

        <ins>/** ----------------------------------------------------------------
         * Reset namespace simulator.
         * -------------------------------------------------------------- */</ins>

        protected <i>$myNodeFile</i> = __FILE__;

        <ins>/** ----------------------------------------------------------------
         * Specify a name of the database table to store channels.
         * -------------------------------------------------------------- */</ins>

        public <i>$table</i> = <strong>'channels'</strong>;

        <ins>/** ----------------------------------------------------------------
         * Define a database table structure.
         * Please note that the default value of <strong>enabled</strong> column is <i>FALSE</i>
         * to avoid displaying entries added with an incomplete number of
         * columns. What is meant here is that if such an incomplete entry
         * was somehow added to the table, it was either a software error
         * or a deliberate act of an attacker.
         * -------------------------------------------------------------- */</ins>

        protected <i>$tableFields</i> = [
                      '`<strong>id</strong>`       BIGINT(20)     NOT NULL  AUTO_INCREMENT                 COMMENT "channel system identifier"',
                      '`<strong>url</strong>`      VARCHAR(255)   NOT NULL                                 COMMENT "page URL relative to the channel list URL"',
                      '`<strong>name</strong>`     VARCHAR(80)    NOT NULL                                 COMMENT "channel name"',
                      '`<strong>meta</strong>`     VARCHAR(512)   NOT NULL                                 COMMENT "text for meta description tag"',
                      '`<strong>avatar</strong>`   VARCHAR(255)   NOT NULL                                 COMMENT "avatar URL relative to the site URL"',
                      '`<strong>info</strong>`     VARCHAR(2048)  NOT NULL                                 COMMENT "channel description"',
                      '`<strong>enabled</strong>`  BOOLEAN        NOT NULL  DEFAULT <i>FALSE</i>                  COMMENT "1 if the channel is visible"',
                      '`<strong>viewed</strong>`   TIMESTAMP      NOT NULL  DEFAULT "<i>2000-01-01 00:00:01</i>"  COMMENT "date up to which the channel was viewed"',
                      '`<strong>created</strong>`  TIMESTAMP      NOT NULL  DEFAULT <i>CURRENT_TIMESTAMP</i>      COMMENT "creation time"'
                  ];

        <ins>/** ----------------------------------------------------------------
         * Define a list of table keys to speed up the database operations
         * related to channels.
         * -------------------------------------------------------------- */</ins>

        protected <i>$tableKeys</i> = [
                      'PRIMARY KEY ( `<strong>id</strong>`      )',
                      'UNIQUE KEY  ( `<strong>url</strong>`     )',
                      'KEY         ( `<strong>name</strong>`    )',
                      'KEY         ( `<strong>enabled</strong>` )',
                      'KEY         ( `<strong>created</strong>` )'
                  ];

        <ins>/** ----------------------------------------------------------------
         * Displays the contents of channel list page.
         * This method handles the following URLs:
         *     https://your.site/channels
         *     https://your.site/channels/A-CHANNEL-URL-PATH
         *                                └───────┬────────┘
         *                                        └─&gt; it is the incoming <i>$params</i>
         * @param  mixed <i>$params</i> The rest of page's URL if detected by the app's routing method.
         * @return bool          <i>TRUE</i>  if the page was rendered successfully.
         *                       <i>FALSE</i> if requested channel is not found and an Error404 page needs to be displayed.
         * -------------------------------------------------------------- */</ins>

        public function <em>run</em> ( <i>$params</i> = <strong>''</strong> ) {
            switch ( <i>$params</i> ) {
                case <strong>''</strong>:
                     <i>$file</i> = '<u>channels-list.tpl</u>';
                     <i>$data</i> = <b>$this</b>-&gt;<em>getItems</em> (         );
                     break;
                default:
                     <i>$file</i> = '<u>channels-page.tpl</u>';
                     <i>$data</i> = <b>$this</b>-&gt;<em>getItem</em>  ( <i>$params</i> );
            }
            return <i>$data</i>
                &amp;&amp; <b>$this</b>-&gt;app-&gt;<em>renderTemplate</em> ( <i>$file</i>,
                                                <strong>'Oops, this site does not have a template for Channels page!'</strong>,
                                                <i>$data</i> );
        }

        <ins>/** ----------------------------------------------------------------
         * Retrieves a channel entry by its URL.
         * Please note that <strong>t1</strong> below is an alias for the primary database
         * table <b>channels</b>, and <strong>t2</strong> is an alias for the secondary table
         * <b>channels_messages</b>.
         * Note also that the filter used below will actually result in the
         * following MySQL query:
         *     SELECT    <strong>t1.*</strong>,
         *               <strong>t2.*</strong>
         *     FROM      <b>channels</b>          AS <strong>t1</strong>
         *     LEFT JOIN <b>channels_messages</b> AS <strong>t2</strong>
         *                                    ON <strong>t2.channel_id</strong> = <strong>t1.id</strong> AND
         *                                       <strong>t2.visible</strong>    = <i>TRUE</i>
         *     WHERE     <strong>t1.url</strong>     = <i>$url</i> AND
         *               <strong>t1.enabled</strong> = <i>TRUE</i>
         *     ORDER BY  <strong>t2.created</strong> <i>DESC</i>
         *     LIMIT     <i>1000000000</i>
         * @param  string     <i>$url</i> The relative URL for the channel you are looking for.
         * @return array|bool      <i>ARRAY</i> on success. It contains a list of rows obtained from a database table.
         *                         <i>FALSE</i> on failure. This means no entries were found.
         * -------------------------------------------------------------- */</ins>

        public function <em>getItem</em> ( <i>$url</i> ) {
            <i>$filter</i> = [
                <strong>'select'</strong>   =&gt; [ <strong>'t1.*'</strong> =&gt; <i>TRUE</i> ,
                                <strong>'t2.*'</strong> =&gt; <i>TRUE</i> ],
                <strong>'join'</strong>     =&gt; [ <b>$this</b>-&gt;<b>messages</b>-&gt;<i>table</i> =&gt; [ <strong>'t2.channel_id'</strong> =&gt; <strong>'t1.id'</strong> ,
                                                            <strong>'t2.visible'</strong>    =&gt; <i>TRUE</i>    ] ],
                <ins>/* where */</ins>     <strong>'t1.url'</strong>     =&gt; <i>$url</i>,
                                <strong>'t1.enabled'</strong> =&gt; <i>TRUE</i>,
                <strong>'orderby'</strong>  =&gt; [ <strong>'t2.created'</strong> =&gt; '<i>desc</i>' ]
            ];
            return <b>$this</b>-&gt;<em>select</em> ( <i>$filter</i>, 0, <i>1000000000</i> );
        }

        <ins>/** ----------------------------------------------------------------
         * Retrieves all channels entries for displaying.
         * These entries are sorted by creation date of their messages. We
         * also add an abstract column to the result entries. This column
         * is called <i>viewer</i>. And it always contains the string <strong>channels/</strong>
         * to print later as the first segment of the channel URL.
         * Please note that <strong>t1</strong> below is an alias for the primary database
         * table <b>channels</b>, and <strong>t2</strong> is an alias for the secondary table
         * <b>channels_messages</b>.
         * Note also that the filter used below will actually result in the
         * following MySQL query:
         *     SELECT    <strong>t1.*</strong>,
         *               <strong>"channels/"</strong>  AS      <i>viewer</i>,
         *               <strong>t2.created</strong>   AS      <i>message_date</i>,
         *               COUNT(<strong>t2.id</strong>) AS      <i>unreaded</i>
         *     FROM      <b>channels</b>          AS <strong>t1</strong>
         *     LEFT JOIN <b>channels_messages</b> AS <strong>t2</strong>
         *                                    ON <strong>t2.channel_id</strong> = <strong>t1.id</strong>     AND
         *                                       <strong>t2.visible</strong>    = <i>TRUE</i>      AND
         *                                       <strong>t2.created</strong>    &gt; <strong>t1.viewed</strong>
         *     WHERE     <strong>t1.enabled</strong> = <i>TRUE</i>
         *     GROUP BY  <strong>t1.id</strong>        <i>ASC</i>
         *     ORDER BY  <i>message_date</i> <i>DESC</i>,
         *               <strong>t1.created</strong>   <i>DESC</i>
         *     LIMIT     <i>1000000000</i>
         * @return array|bool <i>ARRAY</i> on success. Each element is an array, like a row obtained from a database table.
         *                    <i>FALSE</i> on failure. This means no entries were found.
         * -------------------------------------------------------------- */</ins>

        public function <em>getItems</em> ( ) {
            <i>$filter</i> = [
                <strong>'select'</strong>     =&gt; [ <strong>'t1.*'</strong>             =&gt; <i>TRUE</i>           ,
                                  '<strong>"channels/"</strong>'      =&gt; '<i>viewer</i>'       ,
                                  <strong>'t2.created'</strong>       =&gt; '<i>message_date</i>' ,
                                  'COUNT(<strong>`t2`.`id`</strong>)' =&gt; '<i>unreaded</i>'     ],

                <strong>'join'</strong>       =&gt; [ <b>$this</b>-&gt;<b>messages</b>-&gt;<i>table</i> =&gt; [ <strong>'t2.channel_id'</strong> =&gt; <strong>'t1.id'</strong>     ,
                                                              <strong>'t2.visible'</strong>    =&gt; <i>TRUE</i>        ,
                                                              <strong>'&gt; t2.created'</strong>  =&gt; <strong>'t1.viewed'</strong> ] ],
                <ins>/* where */</ins>       <strong>'t1.enabled'</strong> =&gt; <i>TRUE</i>,
                <strong>'groupby'</strong>    =&gt; [ <strong>'t1.id'</strong> =&gt; '<i>asc</i>' ],
                <strong>'orderby'</strong>    =&gt; [ '<i>message_date</i>' =&gt; '<i>desc</i>' ,
                                  <strong>'t1.created'</strong>   =&gt; '<i>desc</i>' ]
            ];
            return <b>$this</b>-&gt;<em>select</em> ( <i>$filter</i>, 0, <i>1000000000</i> );
        }

        <ins>/** ----------------------------------------------------------------
         * Retrieves all channels entries for a sitemap.
         * These entries are sorted by their creation date. Please note that
         * <strong>t1</strong> below is an alias for the primary database table <b>channels</b>.
         * Note also that the filter used below will actually result in the
         * following MySQL query:
         *     SELECT   <strong>t1.url</strong>,
         *              <strong>"channels/"</strong>  AS <i>viewer</i>,
         *     FROM     <b>channels</b> AS <strong>t1</strong>
         *     WHERE    <strong>t1.enabled</strong> = <i>TRUE</i>
         *     ORDER BY <strong>t1.created</strong> <i>DESC</i>
         *     LIMIT    <i>1000000000</i>
         * @return array|bool <i>ARRAY</i> on success. Each element is an array that contains the <strong>url</strong> and <strong>viewer</strong> columns only.
         *                    <i>FALSE</i> on failure. This means no entries were found.
         * -------------------------------------------------------------- */</ins>

        public function <em>getSitemap</em> ( ) {
            <i>$filter</i> = [
                <strong>'select'</strong>  => [ <strong>'t1.url'</strong>      => <i>TRUE</i>     ,
                               '<strong>"channels/"</strong>' => '<i>viewer</i>' ],
                <ins>/* where */</ins>    <strong>'t1.enabled'</strong>  =&gt; <i>TRUE</i>,
                <strong>'orderby'</strong> =&gt; [ <strong>'t1.created'</strong>  =&gt; <i>'desc'</i> ]
            ];
            return <b>$this</b>-&gt;<em>select</em> ( <i>$filter</i>, 0, <i>1000000000</i> );
        }

        <ins>/** ----------------------------------------------------------------
         * Specify demo rows that will be used as default channel entries
         * if the database does not have a table named <b>channels</b>. In this
         * case, all demo rows will be automatically added to the newly
         * created primary table.
         * -------------------------------------------------------------- */</ins>

        <del>protected <i>$demoRows</i> = [ .., SOME, DEMO, ENTRIES, ... ];</del>

        <ins>/** ----------------------------------------------------------------
         * Installs the demo table entries.
         * The need to overwrite this method is due to the presence in your
         * web app of a node module with a primary and secondary table.
         * @param  mixed <i>$params</i> Some parameters if you need.
         * @return bool          <i>TRUE</i>  if at least one new entry has been added.
         *                       <i>FALSE</i> if the table has not changed.
         * -------------------------------------------------------------- */</ins>

        <del>public function <em>install</em> ( $params = NULL ) {</del>
            <del>$this-&gt;messages-&gt;install (         );</del>
            <del>return parent::install   ( $params );</del>
        <del>}</del>
    };</pre>
                </section>
                <section class="message light">
                    <p>Strikethrough indicates lines that are written for demonstration purposes, they may be removed on a production site.</p>
                </section>
                <section class="message">
                    <p>As you can see, the <em>run()</em> method routes the incoming URL to one of the template files that represent the two Views.</p>
                    <p>As you can also see, the <em>getItems()</em> method declares a column <i>unreaded</i> to count new messages that the current user has not yet seen. And a JOIN codition <code><strong>'&gt; t2.created'</strong> =&gt; <strong>'t1.viewed'</strong></code> that filters new messages for counting. However, this demo application still does not implement an API command <u>viewed</u> to dynamically pass information about each viewed message to the backend.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 7.2: Model 1</h4>
                    <p>You should skip this substep because the <b>Api</b> viewer module already contains two methods, <em>getItem()</em> and <em>getItems()</em>, which are Model's methods.</p>
                    <p>It also contains method <em>getSitemap()</em> which is Model's method for the <b>Sitemap</b> viewer module created in step 5 above.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 7.3: subordinate Model 2</h4>
                    <p>Let me remind that you are currently in the <u>Channels</u> folder. Since Model 2 is a subordinate and represents a submodule for working with a secondary database table, this submodule should be placed in the folder of the primary Model 1.</p>
                    <p>So you need to create a subfolder <u>Messages</u> there, and in it a file <u>Messages.php</u> with the following contents.</p>
                    <pre data-title="Messages.php">&lt;?php
<ins>/** ------------------------------------------------------------------------
 * Submodule for working with a secondary table of the <b>Channels</b> module.
 * ---------------------------------------------------------------------- */</ins>

    <em>mimimiInclude</em> ( '<u>ModuleWithTable.php</u>' );
    class <b>MyMimimiChannelsMessages</b> extends <b>MimimiModuleWithTable</b> {

        <ins>/** ----------------------------------------------------------------
         * Specify a name of the database table to store channel messages.
         * -------------------------------------------------------------- */</ins>

        public <i>$table</i> = <strong>'channels_messages'</strong>;

        <ins>/** ----------------------------------------------------------------
         * Define a database table structure.
         * Please note that the default value of <strong>visible</strong> column is <i>FALSE</i>
         * to avoid displaying entries added with an incomplete number of
         * columns. What is meant here is that if such an incomplete entry
         * was somehow added to the table, it was either a software error
         * or a deliberate act of an attacker.
         * -------------------------------------------------------------- */</ins>

        protected <i>$tableFields</i> = [
                      '`<strong>id</strong>`             BIGINT(20)     NOT NULL  AUTO_INCREMENT             COMMENT "message system identifier"',
                      '`<strong>channel_id</strong>`     BIGINT(20)     NOT NULL                             COMMENT "channel identifier"',
                      '`<strong>title</strong>`          VARCHAR(80)    NOT NULL                             COMMENT "message title"',
                      '`<strong>image</strong>`          VARCHAR(255)   NOT NULL                             COMMENT "image URL relative to the site URL"',
                      '`<strong>text</strong>`           VARCHAR(2048)  NOT NULL                             COMMENT "message HTML body"',
                      '`<strong>credits</strong>`        VARCHAR(255)   NOT NULL                             COMMENT "credits URL if you need"',
                      '`<strong>credits_label</strong>`  VARCHAR(40)    NOT NULL                             COMMENT "label for the credits URL"',
                      '`<strong>visible</strong>`        BOOLEAN        NOT NULL  DEFAULT <i>FALSE</i>              COMMENT "1 if the message is visible"',
                      '`<strong>created</strong>`        TIMESTAMP      NOT NULL  DEFAULT <i>CURRENT_TIMESTAMP</i>  COMMENT "creation time"'
                  ];

        <ins>/** ----------------------------------------------------------------
         * Define a list of table keys to speed up the database operations
         * related to channel messages.
         * -------------------------------------------------------------- */</ins>

        protected <i>$tableKeys</i> = [
                      'PRIMARY KEY ( `<strong>id</strong>`         )',
                      'KEY         ( `<strong>channel_id</strong>` )',
                      'KEY         ( `<strong>title</strong>`      )',
                      'KEY         ( `<strong>visible</strong>`    )',
                      'KEY         ( `<strong>created</strong>`    )'
                  ];

        <ins>/** ----------------------------------------------------------------
         * Counts the number of entries with visible status.
         * Please note that <strong>t1</strong> below is an alias for the secondary database
         * table <b>channels_messages</b>. And <strong>t2</strong> is an alias for the primary
         * table <b>channels</b>, which is referenced through <b>$this</b>-&gt;<b>owner</b>.
         * Note also that the filter used below will actually result in the
         * following MySQL query:
         *     SELECT    COUNT(<strong>*</strong>)          AS <i>total</i>
         *     FROM      <b>channels_messages</b> AS <strong>t1</strong>
         *     LEFT JOIN <b>channels</b>          AS <strong>t2</strong>
         *                                    ON <strong>t1.channel_id</strong> = <strong>t2.id</strong>
         *     WHERE     <strong>t1.visible</strong>    = <i>TRUE</i> AND
         *               <strong>t2.enabled</strong>    = <i>TRUE</i> AND
         *               <strong>t1.channel_id</strong> = <i>$id</i>
         *     LIMIT     1
         * @param  int <i>$id</i> The channel identifier for counting.
         * @return int
         * -------------------------------------------------------------- */</ins>

        public function <em>getCount</em> ( <i>$id</i> = 0 ) {
            <i>$filter</i> = [
                <strong>'select'</strong> =&gt; [ 'COUNT(<strong>*</strong>)' =&gt; '<i>total</i>' ],
                <strong>'join'</strong>   =&gt; [ <b>$this</b>-&gt;<b>owner</b>-&gt;<i>table</i> =&gt; [ <strong>'t1.channel_id'</strong> =&gt; <strong>'t2.id'</strong> ] ],
                <ins>/* where */</ins>   <strong>'t1.visible'</strong> =&gt; <i>TRUE</i>,
                              <strong>'t2.enabled'</strong> =&gt; <i>TRUE</i>
            ];
            if ( <i>$id</i> ) {
                <ins>/* where */</ins>   <i>$filter</i>[ <strong>'t1.channel_id'</strong> ] = <i>$id</i>;
            }
            <i>$row</i> = <b>$this</b>-&gt;<em>select</em> ( <i>$filter</i> );
            return empty ( <i>$row</i>[ '<i>total</i>' ] )
                         ? 0
                         : <i>$row</i>[ '<i>total</i>' ];
        }

        <ins>/** ----------------------------------------------------------------
         * Retrieves a random entry with visible status.
         * Please note that <strong>t1</strong> below is an alias for the secondary database
         * table <b>channels_messages</b>. And <strong>t2</strong> is an alias for the primary
         * table <b>channels</b>, which is referenced through <b>$this</b>-&gt;<b>owner</b>.
         * Note also that the filter used below will actually result in the
         * following MySQL query:
         *     SELECT    <strong>t2.name</strong>,
         *               <strong>t1.title</strong>,
         *               <strong>t1.image</strong>,
         *               <strong>t1.text</strong>,
         *               <strong>t1.created</strong>
         *     FROM      <b>channels_messages</b> AS <strong>t1</strong>
         *     LEFT JOIN <b>channels</b>          AS <strong>t2</strong>
         *                                    ON <strong>t1.channel_id</strong> = <strong>t2.id</strong>
         *     WHERE     <strong>t1.visible</strong>    = <i>TRUE</i> AND
         *               <strong>t2.enabled</strong>    = <i>TRUE</i> AND
         *               <strong>t1.channel_id</strong> = <i>$id</i>
         *     LIMIT     1
         *     OFFSET    <i>$offset</i>
         * @param  int        <i>$id</i> The channel identifier to search in.
         * @return array|bool     <i>ARRAY</i> on success. It contains an entry obtained from a database table.
         *                        <i>FALSE</i> on failure. This means no entries were found.
         * -------------------------------------------------------------- */</ins>

        public function <em>getRandom</em> ( <i>$id</i> = 0 ) {
            <i>$count</i> = <b>$this</b>-&gt;<em>getCount</em> ( <i>$id</i> );
            if ( <i>$count</i> ) {
                <i>$filter</i> = [
                    <strong>'select'</strong> =&gt; [ <strong>'t2.name'</strong>    =&gt; <i>TRUE</i> ,
                                  <strong>'t1.title'</strong>   =&gt; <i>TRUE</i> ,
                                  <strong>'t1.image'</strong>   =&gt; <i>TRUE</i> ,
                                  <strong>'t1.text'</strong>    =&gt; <i>TRUE</i> ,
                                  <strong>'t1.created'</strong> =&gt; <i>TRUE</i> ],
                    <strong>'join'</strong>   =&gt; [ <b>$this</b>-&gt;<b>owner</b>-&gt;<i>table</i> =&gt; [
                                                <strong>'t1.channel_id'</strong> =&gt; <strong>'t2.id'</strong> ] ],
                    <ins>/* where */</ins>   <strong>'t1.visible'</strong> =&gt; <i>TRUE</i>,
                                  <strong>'t2.enabled'</strong> =&gt; <i>TRUE</i>
                ];
                if ( <i>$id</i> ) {
                    <ins>/* where */</ins>   <i>$filter</i>[ <strong>'t1.channel_id'</strong> ] = <i>$id</i>;
                }
                <i>$offset</i> = <i>$count</i> &gt; 1
                                 ? mt_rand ( 0, <i>$count</i> - 1 )
                                 : 0;
                return <b>$this</b>-&gt;<em>select</em> ( <i>$filter</i>, <i>$offset</i> );
            }
            return <i>FALSE</i>;
        }

        <ins>/** ----------------------------------------------------------------
         * Specify demo rows that will be used as default message entries
         * if the database does not have a table named <b>channels_messages</b>.
         * In this case, all demo rows will be automatically added to the
         * newly created secondary table.
         * -------------------------------------------------------------- */</ins>

        <del>protected <i>$demoRows</i> = [ .., SOME, DEMO, ENTRIES, ... ];</del>
    };</pre>
                </section>
                <section class="message light">
                    <p>Strikethrough indicates lines that are written for demonstration purposes, they may be removed on a production site.</p>
                </section>

                <section class="message hr">
                    <h4>Substep 7.4: View 1</h4>
                    <p>Markup a template file for rendering HTML content of the primary <b>Channels</b> page. Go to the subfolder <u>Themes/default</u> and create a file <u>channels-list.tpl</u> inside with the following contents.</p>
                    <pre data-title="channels-list.tpl">&lt;?php
    <ins>/** --------------------------------------------------------------------
     * Send headers to the user's browser.
     * ------------------------------------------------------------------ */</ins>

    <em>sendHeaderHTML</em> (   );
    <em>sendStatus200</em>  (   );
    <em>stopIfHead</em>     (   );

    <ins>/** --------------------------------------------------------------------
     * Generate page content.
     * ------------------------------------------------------------------ */</ins>

?&gt;&lt;!DOCTYPE html&gt;
&lt;html lang="en-US" class="<i>channels-page</i>"&gt;
    &lt;head&gt;
        &lt;base href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;"&gt;
        &lt;meta charset="UTF-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
        &lt;meta name="robots"   content="index, follow"&gt;
        &lt;title&gt;
            <strong>Our channels / Five Viewers demo</strong>
        &lt;/title&gt;
        &lt;meta name="description"  content="<strong>This web page is an example of a channel listing.</strong>"&gt;
        &lt;link rel="canonical"     href="&lt;?php <em>printSiteUrl</em>  ( ) ?&gt;<u>channels</u>"&gt;
        &lt;link rel="stylesheet"    href="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>css/styles.css</u>"&gt;
        &lt;link rel="shortcut icon" href="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>images/favicon.ico</u>" type="image/ico"&gt;
    &lt;/head&gt;

    &lt;body&gt;
        &lt;div class="<i>page</i>"&gt;
            &lt;?php <em>mimimiModule</em> ( '<u>snippets/header.tpl</u>' ) ?&gt;
            &lt;?php <em>mimimiModule</em> ( '<u>snippets/menu.tpl</u>'   ) ?&gt;

            &lt;main class="<i>content</i>"&gt;
                &lt;?php
                    if ( <i>$params</i> ) {
                        foreach ( <i>$params</i> as <i>$entry</i> ) {
                            ?&gt;
                            &lt;a class="<i>message</i>" href="&lt;?php <em>printValue</em> ( <i>$entry</i>[ <strong>'viewer'</strong>   ] );
                                                           <em>printValue</em> ( <i>$entry</i>[ <strong>'url'</strong>      ] ) ?&gt;"&gt;

                                &lt;figure class="<i>avatar</i>"&gt;
                                    &lt;img alt="" src="&lt;?php <em>printValue</em> ( <i>$entry</i>[ <strong>'avatar'</strong>   ] ) ?>" loading="lazy" decoding="async"&gt;
                                &lt;/figure&gt;

                                &lt;div data-count="&lt;?php     <em>printValue</em> ( <i>$entry</i>[ <strong>'unreaded'</strong> ] ) ?&gt;"
                                     class="<i>title</i>"&gt;&lt;?php   <em>printValue</em> ( <i>$entry</i>[ <strong>'name'</strong>     ] ) ?&gt;&lt;/div&gt;

                                &lt;div data-date="&lt;?php      <em>printValue</em> ( <i>$entry</i>[ <strong>'created'</strong>  ] ) ?&gt;"
                                     class="<i>info</i>"&gt;&lt;?php    echo         <i>$entry</i>[ <strong>'info'</strong>     ]   ?&gt;&lt;/div&gt;
                            &lt;/a&gt;
                            &lt;?php
                        }
                    }
                ?&gt;
            &lt;/main&gt;

            &lt;aside class="<i>hint</i>"&gt;
                <strong>... an HTML ...</strong>
            &lt;/aside&gt;

            &lt;?php <em>mimimiModule</em> ( '<u>snippets/footer.tpl</u>' ) ?&gt;
        &lt;/div&gt;
        &lt;script src="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>js/scripts.js</u>"&gt;&lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre>
                    <p>Note that this template has an incoming parameter <i>$params</i>, which is an array and it contains a list of non-disabled channels.</p>
                    <p>That array was sent via the <i>$data</i> variable in the <em>run()</em> method (see substep 7.1 above) and looks something like this:</p>
                    <pre>Array (
    [0] =&gt; Array (
            [ <strong>'id'</strong>           ] =&gt; 3,
            [ <strong>'url'</strong>          ] =&gt; 'demo-channel-3',
            [ <strong>'name'</strong>         ] =&gt; 'Hamlet',
            [ <strong>'meta'</strong>         ] =&gt; 'The most famous quotes from William Shakespeare's play Hamlet.',
            [ <strong>'avatar'</strong>       ] =&gt; 'media/demo-posts/five.viewers/avatar-2.png',
            [ <strong>'info'</strong>         ] =&gt; '&lt;p&gt;This demo channel contains the most famous quotes from William Shakespeare\'s play Hamlet.&lt;/p&gt;',
            [ <strong>'enabled'</strong>      ] =&gt; 1,
            [ <strong>'viewed'</strong>       ] =&gt; '2000-01-01 00:00:01',
            [ <strong>'created'</strong>      ] =&gt; '2024-04-16 09:10:11',
            [ <strong>'viewer'</strong>       ] =&gt; 'channels/',
            [ <strong>'message_date'</strong> ] =&gt; '2024-04-16 09:11:21',
            [ <strong>'unreaded'</strong>     ] =&gt; 19
        ),
    [1] =&gt; Array (
            [ <strong>'id'</strong>           ] =&gt; 4,
            [ <strong>'url'</strong>          ] =&gt; 'demo-channel-4',
            [ <strong>'name'</strong>         ] =&gt; 'The Merchant of Venice',
            [ <strong>'meta'</strong>         ] =&gt; 'Quotes from William Shakespeare\'s play The Merchant of Venice.',
            [ <strong>'avatar'</strong>       ] =&gt; '',
            [ <strong>'info'</strong>         ] =&gt; '&lt;p&gt;These quotes listed at this demo channel are included the words of Keats, Milton, Mark Twain, Ralph Waldo Emerson and Oscar Wilde.&lt;/p&gt;',
            [ <strong>'enabled'</strong>      ] =&gt; 1,
            [ <strong>'viewed'</strong>       ] =&gt; '2000-01-01 00:00:01',
            [ <strong>'created'</strong>      ] =&gt; '2024-03-28 08:11:14',
            [ <strong>'viewer'</strong>       ] =&gt; 'channels/',
            [ <strong>'message_date'</strong> ] =&gt; '2024-03-31 09:23:02',
            [ <strong>'unreaded'</strong>     ] =&gt; '12
        ),
    [2] =&gt; Array (
            [ <strong>'id'</strong>           ] =&gt; 2,
            [ <strong>'url'</strong>          ] =&gt; 'demo-channel-2',
            [ <strong>'name'</strong>         ] =&gt; 'Julius Caesar',
            [ <strong>'meta'</strong>         ] =&gt; 'A few famous quotes from William Shakespeare\'s play Julius Caesar.',
            [ <strong>'avatar'</strong>       ] =&gt; 'media/demo-posts/five.viewers/avatar-1.png',
            [ <strong>'info'</strong>         ] =&gt; '&lt;p&gt;For demonstration purposes, let us list in this channel a few famous quotes from William Shakespeare\'s play Julius Caesar.&lt;/p&gt;',
            [ <strong>'enabled'</strong>      ] =&gt; 1,
            [ <strong>'viewed'</strong>       ] =&gt; '2000-01-01 00:00:01',
            [ <strong>'created'</strong>      ] =&gt; '2024-01-19 11:21:32',
            [ <strong>'viewer'</strong>       ] =&gt; 'channels/',
            [ <strong>'message_date'</strong> ] =&gt; '2024-02-02 11:23:43',
            [ <strong>'unreaded'</strong>     ] =&gt; 14
        ),
    [3] =&gt; Array (
            [ <strong>'id'</strong>           ] =&gt; 1,
            [ <strong>'url'</strong>          ] =&gt; 'demo-channel-1',
            [ <strong>'name'</strong>         ] =&gt; 'How to install this app?',
            [ <strong>'meta'</strong>         ] =&gt; 'It is a short instruction that demonstrates how to install this package on a site.',
            [ <strong>'avatar'</strong>       ] =&gt; '',
            [ <strong>'info'</strong>         ] =&gt; '&lt;p&gt;Maybe you want to install such an application on your website. Okay, let\'s put a few messages in this channel demonstrating the installation process.&lt;/p&gt;',
            [ <strong>'enabled'</strong>      ] =&gt; 1,
            [ <strong>'viewed'</strong>       ] =&gt; '2000-01-01 00:00:01',
            [ <strong>'created'</strong>      ] =&gt; '2024-01-02 17:06:55',
            [ <strong>'viewer'</strong>       ] =&gt; 'channels/',
            [ <strong>'message_date'</strong> ] =&gt; '2024-01-02 17:07:56',
            [ <strong>'unreaded'</strong>     ] =&gt; 4
        )
)
</pre>
                </section>
                <section class="message light">
                    <p>By the way, if you need to dump some variable in a template to see its contents, you can use the following directive. For example, <code><em>dumpVar</em>(<i>$params</i>)</code></p>
                </section>
                <section class="message">
                    <nav class="buttons">
                        <a class="btn" href="channels" target="_blank">
                            See Result
                        </a>
                        <a class="btn" href="media/demo-posts/five.viewers/avatar-1.png" rel="nofollow" target="_blank">
                            See avatar-1.png
                        </a>
                        <a class="btn" href="media/demo-posts/five.viewers/avatar-2.png" rel="nofollow" target="_blank">
                            See avatar-2.png
                        </a>
                    </nav>
                </section>

                <section class="message hr">
                    <h4>Substep 7.5: View 2</h4>
                    <p>Markup a template file for rendering HTML content of the secondary <b>Channels</b> page. Go to the subfolder <u>Themes/default</u> and create a file <u>channels-page.tpl</u> inside with the following contents.</p>
                    <pre data-title="channels-page.tpl">&lt;?php
    <ins>/** --------------------------------------------------------------------
     * Send headers to the user's browser.
     * ------------------------------------------------------------------ */</ins>

    <em>sendHeaderHTML</em> (   );
    <em>sendStatus200</em>  (   );
    <em>stopIfHead</em>     (   );

    <ins>/** --------------------------------------------------------------------
    * Get the first message (to display some info about this channel later).
     * ------------------------------------------------------------------ */</ins>

    <i>$channel</i> = reset ( <i>$params</i> );

    <ins>/** --------------------------------------------------------------------
     * Generate page content.
     * ------------------------------------------------------------------ */</ins>

?&gt;&lt;!DOCTYPE html&gt;
&lt;html lang="en-US" class="<i>channel-page</i>"&gt;
    &lt;head&gt;
        &lt;base href="&lt;?php <em>printSiteUrl</em> ( ) ?&gt;"&gt;
        &lt;meta charset="UTF-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
        &lt;meta name="robots"   content="index, follow"&gt;
        &lt;title&gt;
            &lt;?php <em>printValue</em> ( <i>$channel</i>[ <strong>'name'</strong> ] ) ?&gt;
        &lt;/title&gt;
        &lt;meta name="description"  content="&lt;?php <em>printValue</em> ( <i>$channel</i>[ <strong>'meta'</strong> ] ) ?&gt;"&gt;
        &lt;link rel="canonical"     href="&lt;?php <em>printPageUrl</em>  ( ) ?&gt;"&gt;
        &lt;link rel="stylesheet"    href="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>css/styles.css</u>"&gt;
        &lt;link rel="shortcut icon" href="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>images/favicon.ico</u>" type="image/ico"&gt;
    &lt;/head&gt;

    &lt;body&gt;
        &lt;div class="<i>page</i>"&gt;
            &lt;?php <em>mimimiModule</em> ( '<u>snippets/header.tpl</u>' ) ?&gt;
            &lt;?php <em>mimimiModule</em> ( '<u>snippets/menu.tpl</u>'   ) ?&gt;

            &lt;main class="<i>content</i>"&gt;
                &lt;?php
                    if ( <i>$params</i> ) {
                        <i>$params</i> = array_reverse ( <i>$params</i> );
                        foreach ( <i>$params</i> as <i>$entry</i> ) {
                            ?&gt;
                            &lt;section class="<i>message</i>"&gt;
                                &lt;?php
                                    if ( <i>$entry</i>[ <strong>'image'</strong> ] ) {
                                        ?&gt;
                                        &lt;figure class="<i>splash</i>"&gt;
                                            &lt;img src="&lt;?php  <em>printValue</em> ( <i>$entry</i>[ <strong>'image'</strong> ] ) ?&gt;" alt="" loading="lazy" decoding="async"&gt;
                                        &lt;/figure&gt;
                                        &lt;?php
                                    }
                                ?&gt;

                                &lt;div class="<i>info</i>"
                                     data-title="&lt;?php <em>printValue</em> ( <i>$entry</i>[ <strong>'title'</strong>   ] ) ?&gt;"
                                     data-time="&lt;?php  <em>printValue</em> ( <i>$entry</i>[ <strong>'created'</strong> ] ) ?&gt;"
                                     &gt;&lt;?php

                                         if ( <i>$entry</i>[ <strong>'text'</strong>          ]
                                         ||   <i>$entry</i>[ <strong>'credits'</strong>       ]
                                         ||   <i>$entry</i>[ <strong>'credits_label'</strong> ] ) {

                                             echo <i>$entry</i>[ <strong>'text'</strong> ];
                                             ?&gt;

                                             &lt;a class="<i>btn</i>"
                                                href="&lt;?php <em>printValue</em> ( <i>$entry</i>[ <strong>'credits'</strong> ] ) ?&gt;"
                                                rel="nofollow noopener noreferrer"
                                                tabindex="-1"
                                                target="_blank"&gt;&lt;?php <em>printValue</em> ( <i>$entry</i>[ <strong>'credits_label'</strong> ] ) ?&gt;&lt;/a&gt;
                                             &lt;?php
                                         }
                                     ?&gt;&lt;/div&gt;
                            &lt;/section&gt;
                            &lt;?php
                        }
                    }
                ?&gt;
            &lt;/main&gt;

            &lt;aside class="<i>hint</i>"&gt;
                &lt;figure class="<i>avatar</i>"&gt;
                    &lt;img alt="" src="&lt;?php <em>printValue</em> ( <i>$channel</i>[ <strong>'avatar'</strong> ] ) ?&gt;" loading="lazy" decoding="async"&gt;
                &lt;/figure&gt;
                &lt;h6&gt;&lt;?php <em>printValue</em> ( <i>$channel</i>[ <strong>'name'</strong> ] ) ?&gt;&lt;/h6&gt;
                &lt;?php     echo         <i>$channel</i>[ <strong>'info'</strong> ]   ?&gt;

                <strong>... an HTML ...</strong>
            &lt;/aside&gt;

            &lt;?php <em>mimimiModule</em> ( '<u>snippets/footer.tpl</u>' ) ?&gt;
        &lt;/div&gt;
        &lt;script src="&lt;?php <em>printThemeUrl</em> ( ) ?&gt;<u>js/scripts.js</u>"&gt;&lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre>
                    <p>This template also has an incoming parameter <i>$params</i>, which is an array. It contains a list of non-disabled messages for the currently browsed channel.</p>
                    <nav class="buttons">
                        <a class="btn" href="channels/demo-channel-2" target="_blank">
                            See Result
                        </a>
                    </nav>
                    <p>Please note that we reverse the incoming array <i>$params</i> to display messages in ascending date order because <em>getItem()</em> method read them in descending order.</p>
                </section>

                <section class="message hr">
                    <h4>What does your build look like now?</h4>
                    <pre class="filelist">├─> [-] <strong>five.viewers</strong>
│       ├─> <strong>Api</strong>
│       │   └─> <u>Api.php</u>
│       │       <i>├─&lt;─ run()</i>
│       │       <i>├─&lt;─ routeVersion1()</i>
│       │       <i>├─&lt;─ doRandomVersion1()</i>
│       │       <i>├─&lt;─ sendJsonHeaders()</i>
│       │       <i>└─&lt;─ sendJsonData()</i>
│       ├─> <strong class="newly">Channels</strong>
│       │   ├─> <strong class="newly">Messages</strong>
│       │   │   └─> <u class="newly">Messages.php</u>
│       │   │       <i class="newly">├─&lt;─ property $table</i>
│       │   │       <i class="newly">├─&lt;─ property $tableFields</i>
│       │   │       <i class="newly">├─&lt;─ property $tableKeys</i>
│       │   │       <i class="newly">├─&lt;─ getCount()</i>
│       │   │       <i class="newly">├─&lt;─ getRandom()</i>
│       │   │       <i class="newly">└─&lt;─ property $demoRows</i>
│       │   └─> <u class="newly">Channels.php</u>
│       │       <i class="newly">├─&lt;─ property $myNodeFile</i>
│       │       <i class="newly">├─&lt;─ property $table</i>
│       │       <i class="newly">├─&lt;─ property $tableFields</i>
│       │       <i class="newly">├─&lt;─ property $tableKeys</i>
│       │       <i class="newly">├─&lt;─ run()</i>
│       │       <i class="newly">├─&lt;─ getItem()</i>
│       │       <i class="newly">├─&lt;─ getItems()</i>
│       │       <i class="newly">├─&lt;─ getSitemap()</i>
│       │       <i class="newly">├─&lt;─ property $demoRows</i>
│       │       <i class="newly">└─&lt;─ install()</i>
│       ├─> <strong>Error404</strong>
│       │   └─> <u>Error404.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Home</strong>
│       │   └─> <u>Home.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>RobotsTxt</strong>
│       │   └─> <u>RobotsTxt.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Sitemap</strong>
│       │   └─> <u>Sitemap.php</u>
│       │       <i>└─&lt;─ run()</i>
│       ├─> <strong>Themes</strong>
│       │   └─> <strong>default</strong>
│       │       ├─> <strong>css</strong>
│       │       │   └─> <u>styles.css</u>
│       │       ├─> <strong>images</strong>
│       │       │   ├─> <u>favicon.ico</u>
│       │       │   └─> <u class="newly">no-avatar.png</u>
│       │       ├─> <strong>js</strong>
│       │       │   └─> <u>scripts.js</u>
│       │       │       <i>├─&lt;─ findTimes()</i>
│       │       │       <i>├─&lt;─ extractTime()</i>
│       │       │       <i>├─&lt;─ fixTimes()</i>
│       │       │       <i>├─&lt;─ displayRandom()</i>
│       │       │       <i>├─&lt;─ renderMessage()</i>
│       │       │       <i class="newly">├─&lt;─ findDates()</i>
│       │       │       <i class="newly">├─&lt;─ extractDate()</i>
│       │       │       <i class="newly">├─&lt;─ fixDates()</i>
│       │       │       <i>├─&lt;─ findPollers()</i>
│       │       │       <i>├─&lt;─ startPollers()</i>
│       │       │       <i>├─&lt;─ pollingFor()</i>
│       │       │       <i>├─&lt;─ getPollerUrl()</i>
│       │       │       <i>├─&lt;─ getPollerOptions()</i>
│       │       │       <i>├─&lt;─ getPollerTime()</i>
│       │       │       <i>├─&lt;─ loadDocument()</i>
│       │       │       <i class="newly">├─&lt;─ constant NO_AVATAR_IMAGE</i>
│       │       │       <i class="newly">├─&lt;─ findAvatars()</i>
│       │       │       <i class="newly">└─&lt;─ fixAvatars()</i>
│       │       ├─> <strong>snippets</strong>
│       │       │   ├─> <u>footer.tpl</u>
│       │       │   ├─> <u>header.tpl</u>
│       │       │   └─> <u>menu.tpl</u>
│       │       ├─> <u class="newly">channels-list.tpl</u>
│       │       ├─> <u class="newly">channels-page.tpl</u>
│       │       ├─> <u>error-404.tpl</u>
│       │       ├─> <u>home.tpl</u>
│       │       ├─> <u>robots-txt.tpl</u>
│       │       └─> <u>sitemap.tpl</u>
│       └─> <u>Application.php</u>
│           <i>├─&lt;─ property $viewerCollector</i>
│           <i>├─&lt;─ property $pollerCollector</i>
│           <i>├─&lt;─ property $systemCollector</i>
│           <i>├─&lt;─ property $allowedPollers</i>
│           <i>├─&lt;─ property $allowedViewers</i>
│           <i>└─&lt;─ property $myNodeFile</i>
├─> [+] <strong>media</strong>
├─> [+] <strong>mimimi.core</strong>
├─> [+] <strong>mimimi.install</strong>
├─> [+] <strong>mimimi.modules</strong>
├─> [+] <strong>newspaper</strong>
├─> [+] <strong>tiny.news.feed</strong>
├─> <u>.htaccess</u>
├─> <u>favicon.ico</u>
└─> <u>index.php</u></pre>
                    <p>A blinking green dot at the end of the file name indicates recently created folders or files. Green strings indicates functions or methods added to the file.</p>
                    <nav class="buttons">
                        <a class="btn" href="<?php printThemeUrl ( ) ?>images/no-avatar.png" rel="nofollow" target="_blank">
                            See no-avatar.png
                        </a>
                    </nav>
                </section>

                <section class="message interlude hr">
                    <h2>That's all, guys!</h2>
                    <p>Now you have the knowledge to quickly create a simple website similar to channels in social messengers.</p>
                    <p>Of course, it is very primitive and does not even contain functions for editing the channel list or messages in channels. These functions were simulated using demo entries placed in the property <i>$demoRows</i> of the viewer module <b>Channels</b> and its submodule <b>Messages</b>.</p>
                    <p>Our goal was not to write a full-fledged application for you. We only showed an example, and you can use it as a skeleton for your goals.</p>
                    <figure>
                        <img src="media/demo-posts/five.viewers/home-8.jpg" alt="">
                        <figcaption>
                            <a href="https://unsplash.com/photos/group-of-people-sitting-on-boat-dock-zoIBh4s0x6Y" rel="nofollow noopener noreferrer" tabindex="-1" target="_blank">
                                Photo by Yanapi Senaud on Unsplash
                            </a>
                        </figcaption>
                    </figure>
                    <p>And there remains one last question that also needs to be answered.</p>
                    <nav class="buttons">
                        <a class="btn" href="channels/demo-channel-1">
                            How To Install?
                        </a>
                    </nav>
                </section>
            </main>

            <aside class="hint">
                <h6>To developer</h6>
                <p>If you want to learn how the <b>Home</b> page works, please see these files:</p>
                <ul>
                    <li>
                        <u>Application.php</u>
                        <ul>
                            <li>
                                <u>mimimi.core/Website.php</u>
                                <ul>
                                    <li>
                                        <u>mimimi.core/Application.php</u>
                                        <ul>
                                            <li>
                                                <u>mimimi.core/NodeModule.php</u>
                                                <ul>
                                                    <li><u>mimimi.core/Module.php</u></li>
                                                </ul>
                                            </li>
                                        </ul>
                                    </li>
                                    <li>
                                        <u>mimimi.modules/Url/Url.php</u>
                                        <ul>
                                            <li><u>mimimi.core/Module.php</u></li>
                                        </ul>
                                    </li>
                                    <li>
                                        <u>mimimi.modules/Helper/Helper.php</u>
                                        <ul>
                                            <li><u>mimimi.core/Module.php</u></li>
                                        </ul>
                                    </li>
                                    <li><u>mimimi.core/Routines.php</u></li>
                                </ul>
                            </li>
                        </ul>
                    </li>
                    <li>
                        <u>Home/Home.php</u>
                        <ul>
                            <li><u>mimimi.core/Module.php</u></li>
                        </ul>
                    </li>
                    <li>
                        <u>Themes/default/home.tpl</u>
                        <ul>
                            <li><u>Themes/default/snippets/header.tpl</u></li>
                            <li><u>Themes/default/snippets/menu.tpl</u></li>
                            <li><u>Themes/default/snippets/footer.tpl</u></li>
                            <li><u>Themes/default/css/styles.css</u></li>
                            <li><u>Themes/default/js/scripts.js</u></li>
                            <li><u>Themes/default/images/favicon.ico</u></li>
                        </ul>
                    </li>
                </ul>
            </aside>

            <?php mimimiModule ( 'snippets/footer.tpl' ) ?>
        </div>
        <script src="<?php printThemeUrl ( ) ?>js/scripts.js"></script>
    </body>
</html>
