<?php /* =======================================================
|                                                              |
|   MiMiMi framework & CMS                                     |
|       Copyright 2022 MiMiMi Community                        |
|           > www.mimimi.software                              |
|       Licensed under CC BY 4                                 |
|           > www.creativecommons.org/licenses/by/4.0          |
|                                                              |
|   --------------------------------------------------------   |
|                                                              |
|   This file is a class for the BAN module. It checks if      |
|   the visitor has restrictions.                              |
|                                                              |
|   --------------------------------------------------------   |
|                                                              |
|   For example, let's check the current visitor for bans and  |
|   show a message if this check was successful:               |
|                                                              |
|       $cms = mimimiCreate('application');                    |
|       $cms->ban->run();                                      |
|       echo 'You are not a banned visitor!';                  |
|                                                              |
============================================================= */

    /* =========================================================
    |                                                          |
    |   Load the basis for system DB modules from CORE folder  |
    |                                                          |
    ========================================================= */

    mimimiInclude('ModuleWithTable.php');

    /* =========================================================
    |                                                          |
    |   Declare the BAN module's class                         |
    |                                                          |
    ========================================================= */

    class MimimiBan extends MimimiModuleWithTable {

        /* -------------------------------------
        |                                      |
        |   The module's table details         |
        |                                      |
        ------------------------------------- */

        protected $table        = 'bans';
        protected $tableFields  = array(
                      '`id`       BIGINT                 NOT NULL  AUTO_INCREMENT  COMMENT "record identifier"',
                      '`enabled`  TINYINT(1)             NOT NULL  DEFAULT 1       COMMENT "0 if this is a disabled record"',
                      '`verdict`  VARCHAR(50)            NOT NULL  DEFAULT ""      COMMENT "a word that indicates who the visitor is"',
                      '`until`    INT          UNSIGNED  NOT NULL  DEFAULT 0       COMMENT "verdict is valid until this timestamp"',
                      '`code`     SMALLINT     UNSIGNED  NOT NULL  DEFAULT 403     COMMENT "HTTP response code to the visitor"',
                      '`message`  VARCHAR(4000)          NOT NULL  DEFAULT ""      COMMENT "response text to the visitor"',
                      '`ip`       VARCHAR(250)           NOT NULL  DEFAULT ""      COMMENT "verdict is valid for the visitor IP"',
                      '`host`     VARCHAR(250)           NOT NULL  DEFAULT ""      COMMENT "OR verdict is valid for the visitor host"',
                      '`agent`    VARCHAR(500)           NOT NULL  DEFAULT ""      COMMENT "OR verdict is valid for the visitor browser agent"',
                      '`remark`   VARCHAR(4000)          NOT NULL  DEFAULT ""      COMMENT "some administrative notes about this record"'
                  );
        protected $tableKeys    = array(
                      'PRIMARY KEY   (`id`)',
                      'KEY `enabled` (`enabled`)',
                      'KEY `verdict` (`verdict`)',
                      'KEY `until`   (`until`)',
                      'KEY `code`    (`code`)',
                      'KEY `ip`      (`ip`)',
                      'KEY `host`    (`host`)',
                      'KEY `agent`   (`agent`)'
                  );

        /* -------------------------------------
        |                                      |
        |   The last result of the RUN method  |
        |                                      |
        ------------------------------------- */

        protected $lastVerdict = null;

        /* =====================================================
        |                                                      |
        |   Get a banning verdict for this visitor             |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Input parameters:                                  |
        |       $params = ARRAY = information about visitor:   |
        |           'ip'    = STRING = his IP address          |
        |           'host'  = STRING = his host                |
        |           'agent' = STRING = his browser agent       |
        |           ...                                        |
        |           and so on                                  |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Output parameters:                                 |
        |       ARRAY = information about verdict              |
        |       FALSE = if this is exemplary visitor           |
        |                                                      |
        ===================================================== */

        protected function getVerdict ( $params ) {
            if ($this->cms->has->db) {

                /* -----------------------------
                |                              |
                |   Parse a visitor parameters |
                |                              |
                ----------------------------- */

                $ip = empty($params['ip'])
                      ? ''
                      : $params['ip'];
                $host = empty($params['host'])
                        ? ''
                        : $params['host'];
                $agent = empty($params['agent'])
                         ? ''
                         : $params['agent'];

                /* -----------------------------
                |                              |
                |   If any parameter exists    |
                |                              |
                ----------------------------- */

                $now = time();
                while ($ip || $host || $agent) {

                    /* -------------------------
                    |                          |
                    |   Prepare a query        |
                    |                          |
                    |   --------------------   |
                    |                          |
                    |   The QUESTION simbols   |
                    |   are placeholders to    |
                    |   replace with a related |
                    |   parameter.             |
                    |                          |
                    ------------------------- */

                    $query = 'SELECT *  ' .
                             'FROM   `__' . $this->table . '`  ' .
                             'WHERE  `enabled` = 1  AND  ' .
                                   ' ( ' .
                                       ' `until` = 0  OR ' .
                                       ' `until` > ?' .
                                   ' ) AND ( ' .
                                       ' `ip`    != ""  AND  `ip`    = ?  OR ' .
                                       ' `host`  != ""  AND  `host`  = ?  OR ' .
                                       ' `agent` != ""  AND  `agent` = ?' .
                                   ' ) ' .
                             'LIMIT 1';

                    /* -------------------------
                    |                          |
                    |   Execute it             |
                    |                          |
                    |   --------------------   |
                    |                          |
                    |   The second argument    |
                    |   is an array of the     |
                    |   related parameters for |
                    |   the QUESTION symbols.  |
                    |                          |
                    ------------------------- */

                    $result = $this->cms->db->query(
                        $query,
                        array(
                            $now,
                            $ip,
                            $host,
                            $agent
                        )
                    );

                    /* -------------------------
                    |                          |
                    |   Extract a verdict      |
                    |                          |
                    |   --------------------   |
                    |                          |
                    |   We are fetching it as  |
                    |   associative array.     |
                    |   And closing DB line.   |
                    |                          |
                    ------------------------- */

                    if ($result) {
                        $row = $result->fetch(
                                   PDO::FETCH_ASSOC
                               );
                        $result->closeCursor();

                        /* ---------------------
                        |                      |
                        |   Return the verdict |
                        |                      |
                        --------------------- */

                        return isset($row['verdict'])
                               ? $row
                               : FALSE;
                    }

                    /* -------------------------
                    |                          |
                    |   Else go to parent rule |
                    |                          |
                    ------------------------- */

                    $ip = preg_replace('/[^.:]*[.:]?$/', '', $ip);
                    $host = preg_replace('/^[.]?[^.]*/', '', $host);
                    $agent = '';
                }
            }
            return FALSE;
        }

        /* =====================================================
        |                                                      |
        |   Perform some actions required to verify the        |
        |   current visitor                                    |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Input parameters:                                  |
        |       $params = MIXED = some parameters if you need  |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Output parameters:                                 |
        |       ARRAY = information about verdict              |
        |       FALSE = if this is exemplary visitor           |
        |                                                      |
        ===================================================== */

        public function run ( $params = '' ) {
            if (is_null($this->lastVerdict)) {
                $this->lastVerdict = FALSE;

                /* -----------------------------
                |                              |
                |   Verify the visitor         |
                |                              |
                ----------------------------- */

                if ($this->cms->has->visitor) {
                    $user = $this->cms->visitor->run();
                    $data = $this->getVerdict($user);

                    /* -------------------------
                    |                          |
                    |   If he is not exemplary |
                    |                          |
                    ------------------------- */

                    $needStop = !empty($data['message']) ||
                                !empty($data['code']);
                    if ($needStop) {

                        /* ---------------------
                        |                      |
                        |   Do human-readable  |
                        |                      |
                        --------------------- */

                        $data['until'] = date(
                            'Y.m.d H:i:s',
                            $data['until']
                        );

                        /* ---------------------
                        |                      |
                        |   Inform your masters|
                        |                      |
                        --------------------- */

                        if ($this->cms->has->informer) {
                            $this->cms->informer->run(
                                array(
                                    'subject'  => 'Visit of banned user on [site]',
                                    'message'  => 'This visitor has been banned for some reason. See details below.',
                                    'email'    => MIMIMI_EMAILS_FOR_SECURITY_MASTER,
                                    'sms'      => MIMIMI_PHONES_FOR_SECURITY_MASTER,
                                    'telegram' => MIMIMI_TELEGRAMS_FOR_SECURITY_MASTER,
                                    'details'  => array(
                                                      'Visitor' => $user,
                                                      'Verdict' => $data
                                                  )
                                )
                            );
                        }

                        /* ---------------------
                        |                      |
                        |   Notify the visitor |
                        |                      |
                        --------------------- */

                        mimimiStop(
                            $data['message'],
                            $data['code']
                        );
                    }

                    /* -------------------------
                    |                          |
                    |   Remember for the future|
                    |                          |
                    ------------------------- */

                    $this->lastVerdict = $data;
                }
            }

            /* ---------------------------------
            |                                  |
            |   Return the verdict             |
            |                                  |
            --------------------------------- */

            return $this->lastVerdict;
        }
    };

    /* =========================================================
    |                                                          |
    |   Block trailing injections                              |
    |                                                          |
    ========================================================= */

    return;