<?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 DATABASE module. It is used   |
|   to read records from your database.                        |
|                                                              |
|   --------------------------------------------------------   |
|                                                              |
|   For example, let's get all records from EXAMPLE table:     |
|                                                              |
|       $cms = mimimiCreate('application');                    |
|       $result = $cms->db->query('SELECT * FROM __example');  |
|                                                              |
============================================================= */

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

    mimimiInclude('Module.php');

    /* =========================================================
    |                                                          |
    |   Declare the child class                                |
    |                                                          |
    ========================================================= */

    class MimimiDb extends MimimiModule {
        public $error = 0;
        public $errorInfo = [];
        protected $db = FALSE;

        /* =====================================================
        |                                                      |
        |   Connect to the specified database                  |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Input parameters:                                  |
        |       $params = ARRAY   = list of parameters:        |
        |                               'driver'               |
        |                               'host'                 |
        |                               'port'                 |
        |                               'user'                 |
        |                               'password'             |
        |                               'name'                 |
        |                               'charset'              |
        |                               'collation'            |
        |       $quiet  = BOOLEAN = FALSE if inform your       |
        |                                 masters on error     |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Output parameters:                                 |
        |       TRUE   = if connection is not required         |
        |       OBJECT = if connected                          |
        |       FALSE  = if no required parameter              |
        |       STRING = if error                              |
        |                                                      |
        ===================================================== */

        public function connect ( $params, $quiet = TRUE ) {
            $result = FALSE;
            if (isset($params['driver'])
            && isset($params['host'])
            && isset($params['port'])
            && isset($params['user'])
            && isset($params['password'])
            && isset($params['name'])
            && isset($params['charset'])
            && isset($params['collation'])) {
                $subject = 'Problem of database configuration on [site]';

                /* -----------------------------
                |                              |
                |   Test the DB configuration  |
                |                              |
                ----------------------------- */

                $driver = $params['driver'];
                switch($driver) {
                    case '':
                        return TRUE;
                    case 'cubrid':
                    case 'dblib':
                    case 'firebird':
                    case 'ibm':
                    case     'odbc-ibm':
                    case 'informix':
                    case 'mssql':
                    case 'mysql':
                    case 'odbc':
                    case 'oci':
                    case 'oracle':
                    case 'pgsql':
                    case 'sqlsrv':
                    case 'sybase':
                        if ($params['host'] == '') {
                            $msg = 'ERROR: Database host is an empty string for the "' . $driver . '" driver!';
                            if ($quiet) {
                                return $msg;
                            }
                            if ($this->cms->has->informer) {
                                $this->cms->informer->run(
                                    array(
                                        'subject'  => $subject,
                                        'message'  => $msg,
                                        'email'    => MIMIMI_EMAILS_FOR_WEBMASTER,
                                        'sms'      => MIMIMI_PHONES_FOR_WEBMASTER,
                                        'telegram' => MIMIMI_TELEGRAMS_FOR_WEBMASTER
                                    )
                                );
                            }
                            mimimiStop($msg);
                        }
                    case 'odbc':
                    case     'odbc-access':
                    case 'sqlite':
                        if ($params['name'] == '') {
                            $msg = 'ERROR: Database name is an empty string for the "' . $driver . '" driver!';
                            if ($quiet) {
                                return $msg;
                            }
                            if ($this->cms->has->informer) {
                                $this->cms->informer->run(
                                    array(
                                        'subject'  => $subject,
                                        'message'  => $msg,
                                        'email'    => MIMIMI_EMAILS_FOR_WEBMASTER,
                                        'sms'      => MIMIMI_PHONES_FOR_WEBMASTER,
                                        'telegram' => MIMIMI_TELEGRAMS_FOR_WEBMASTER
                                    )
                                );
                            }
                            mimimiStop($msg);
                        }
                        break;
                    default:
                        $msg = 'ERROR: The "' . $driver . '" driver is no support!';
                        if ($quiet) {
                            return $msg;
                        }
                        if ($this->cms->has->informer) {
                            $this->cms->informer->run(
                                array(
                                    'subject'  => $subject,
                                    'message'  => $msg,
                                    'email'    => MIMIMI_EMAILS_FOR_WEBMASTER,
                                    'sms'      => MIMIMI_PHONES_FOR_WEBMASTER,
                                    'telegram' => MIMIMI_TELEGRAMS_FOR_WEBMASTER
                                )
                            );
                        }
                        mimimiStop($msg);
                }

                /* -----------------------------
                |                              |
                |   Try to collect parameters  |
                |                              |
                ----------------------------- */

                switch($driver) {
                    case 'cubrid':
                    case 'dblib':
                    case 'mssql':
                    case 'mysql':
                    case 'pgsql':
                    case 'sybase':
                        $port = $params['port'] != ''
                                ? 'port=' . $params['port'] . ';'
                                : '';
                        $name = $driver . ':' .
                                'host=' . $params['host'] . ';' .
                                $port .
                                'dbname=' . $params['name'] . ';';
                        break;
                    case 'firebird':
                        $port = $params['port'] != ''
                                ? '/' . $params['port']
                                : '';
                        $name = $driver . ':' .
                                'dbname=' . $params['host'] . $port . ':' .
                                            $params['name'];
                        break;
                    case 'odbc-ibm':
                        $driver = 'odbc';
                    case 'ibm':
                        $port = $params['port'] != ''
                                ? 'PORT=' . $params['port'] . ';'
                                : '';
                        $name = $driver . ':' .
                                'DRIVER={IBM DB2 ODBC DRIVER};' .
                                'HOSTNAME=' . $params['host'] . ';' .
                                $port .
                                'DATABASE=' . $params['name'] . ';' .
                                'PROTOCOL=TCPIP;';
                        break;
                    case 'informix':
                        $port = $params['port'] != ''
                                ? ' service=' . $params['port'] . ';'
                                : '';
                        $name = $driver . ':' .
                                'host=' . $params['host'] . ';' .
                                $port .
                                ' database=' . $params['name'] . ';' .
                                ' server=ids_server;' .
                                ' protocol=onsoctcp;';
                        break;
                    case 'oracle':
                        $driver = 'oci';
                    case 'oci':
                        $port = $params['port'] != ''
                                ? ':' . $params['port']
                                : '';
                        $name = $driver . ':' .
                                'dbname=//' . $params['host'] . $port . '/' .
                                              $params['name'];
                        break;
                    case 'odbc':
                        $name = $driver . ':' .
                                $params['name'];
                        break;
                    case 'odbc-access':
                        $name = 'odbc:' .
                                'Driver={Microsoft Access Driver (*.mdb)};' .
                                'Dbq=' . $params['name'] . ';';
                        break;
                    case 'sqlite':
                        $name = $driver . ':' .
                                $params['name'];
                        break;
                    case 'sqlsrv':
                        $port = $params['port'] != ''
                                ? ',port=' . $params['port']
                                : '';
                        $name = $driver . ':' .
                                'Server=' . $params['host'] . $port . ';' .
                                'Database=' . $params['name'] . ';';
                        break;
                }

                /* -----------------------------
                |                              |
                |   Try to connect             |
                |                              |
                ----------------------------- */

                try {
                    $result = new PDO(
                        $name,
                        $params['user'],
                        $params['password']
                    );
                    if ($params['charset'] != '') {
                        $result->exec('SET CHARACTER SET "' . $params['charset'] . '"');
                        if ($params['collation'] != '') {
                            $result->exec('SET NAMES "' . $params['charset'] . '" ' .
                                              'COLLATE "' . $params['collation'] . '"');
                        }
                    }
                    $result->setAttribute(
                        PDO::ATTR_DEFAULT_FETCH_MODE,
                        PDO::FETCH_OBJ
                    );
                } catch (PDOException $error) {
                    $result = FALSE;

                    /* -------------------------
                    |                          |
                    |   Else prepare email     |
                    |   details                |
                    |                          |
                    ------------------------- */

                    $msg = 'ERROR: No connection to the database.';
                    $details = array(
                        'Driver'   => $params['driver'],
                        'Host'     => $params['host'],
                        'Port'     => $params['port'],
                        'Database' => $params['name'],
                        'User'     => $params['user'],
                        'Password' => '********** (see on the site)',
                        'Result'   => preg_replace(
                                          '/[\s\x00-\x20\xA0]+/u',
                                          ' ',
                                          $error->getMessage()
                                      )
                    );

                    /* -------------------------
                    |                          |
                    |   If QUIET mode          |
                    |                          |
                    ------------------------- */

                    if ($quiet) {
                        return $msg . ' ' .
                               $details['Result'];
                    }

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

                    if ($this->cms->has->informer) {
                        $this->cms->informer->run(
                            array(
                                'subject'  => $subject,
                                'message'  => $msg,
                                'email'    => MIMIMI_EMAILS_FOR_WEBMASTER,
                                'sms'      => MIMIMI_PHONES_FOR_WEBMASTER,
                                'telegram' => MIMIMI_TELEGRAMS_FOR_WEBMASTER,
                                'details'  => $details
                            )
                        );
                    }

                    /* -------------------------
                    |                          |
                    |   Show the error to user |
                    |                          |
                    ------------------------- */

                    mimimiStop($msg);
                }
            }
            return $result;
        }

        /* =====================================================
        |                                                      |
        |   Perform some actions required to connect the       |
        |   database                                           |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Input parameters:                                  |
        |       $params = MIXED = some parameters if you need  |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Output parameters:                                 |
        |       MIXED = some result if you need                |
        |                                                      |
        ===================================================== */

        public function run ( $params = '' ) {
            if (empty($this->db)) {
                $this->db = $this->connect(
                                array(
                                    'driver'    => MIMIMI_DATABASE_DRIVER,
                                    'host'      => MIMIMI_DATABASE_HOST,
                                    'port'      => MIMIMI_DATABASE_PORT,
                                    'user'      => MIMIMI_DATABASE_USER,
                                    'password'  => MIMIMI_DATABASE_PASSWORD,
                                    'name'      => MIMIMI_DATABASE_NAME,
                                    'charset'   => MIMIMI_DATABASE_CHARSET,
                                    'collation' => MIMIMI_DATABASE_COLLATION
                                ), FALSE
                            );
            }
        }

        /* =====================================================
        |                                                      |
        |   Execute an SQL statement and return the object     |
        |   associated with that statement                     |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Input parameters:                                  |
        |       $query  = STRING = the SQL statement           |
        |       $params = ARRAY  = values to insert instead    |
        |                          of QUESTION signs in the    |
        |                          SQL statement               |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Output parameters:                                 |
        |       OBJECT = if success                            |
        |       FALSE  = if failure                            |
        |                                                      |
        ===================================================== */

        public function query ( $query, $params = array() ) {
            $this->run();
            if (!empty($this->db)) {
                $this->error = 0;
                $this->errorInfo = [0, NULL, NULL];

                /* -----------------------------
                |                              |
                |   Set the table prefix       |
                |                              |
                ----------------------------- */

                $marker = '/([\s`,=\(\+-])__([a-z][a-z0-9_]*[\s`,.\);])/ui';
                $prefix = '$1' . MIMIMI_DATABASE_TABLE_PREFIX . '$2';
                $query = preg_replace($marker, $prefix, $query);

                /* -----------------------------
                |                              |
                |   Execute a query            |
                |                              |
                ----------------------------- */

                $object = $this->db->prepare($query);
                if ($object) {
                    try {
                        $result = $object->execute($params);
                    } catch (Exception $e) {
                        $result = FALSE;
                    }
                    if ($result) {

                        /* ---------------------
                        |                      |
                        |   Return the object  |
                        |                      |
                        --------------------- */

                        return $object;

                    /* -------------------------
                    |                          |
                    |   Else get an error      |
                    |                          |
                    ------------------------- */

                    } else {
                        $this->error = $object->errorCode();
                        $this->errorInfo = $object->errorInfo();
                    }
                } else {
                    $this->error = $this->db->errorCode();
                    $this->errorInfo = $this->db->errorInfo();
                }
            }
            return FALSE;
        }

        /* =====================================================
        |                                                      |
        |   Return the ID of the last inserted row             |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Input parameters:                                  |
        |       NONE                                           |
        |                                                      |
        |   ------------------------------------------------   |
        |                                                      |
        |   Output parameters:                                 |
        |       INTEGER = row identifier                       |
        |       FALSE   = if failure                           |
        |                                                      |
        ===================================================== */

        public function lastInsertId () {
            $this->run();
            return empty($this->db)
                   ? FALSE
                   : $this->db->lastInsertId();
        }
    };

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

    return;