xref: /plugin/combo/ComboStrap/Sqlite.php (revision 37748cd8654635afbeca80942126742f0f4cc346)
1*37748cd8SNickeau<?php
2*37748cd8SNickeau/**
3*37748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4*37748cd8SNickeau *
5*37748cd8SNickeau * This source code is licensed under the GPL license found in the
6*37748cd8SNickeau * COPYING  file in the root directory of this source tree.
7*37748cd8SNickeau *
8*37748cd8SNickeau * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9*37748cd8SNickeau * @author   ComboStrap <support@combostrap.com>
10*37748cd8SNickeau *
11*37748cd8SNickeau */
12*37748cd8SNickeau
13*37748cd8SNickeaunamespace ComboStrap;
14*37748cd8SNickeau
15*37748cd8SNickeaurequire_once(__DIR__ . '/LogUtility.php');
16*37748cd8SNickeau
17*37748cd8SNickeauuse helper_plugin_sqlite;
18*37748cd8SNickeauuse helper_plugin_sqlite_adapter_pdosqlite;
19*37748cd8SNickeau
20*37748cd8SNickeauclass Sqlite
21*37748cd8SNickeau{
22*37748cd8SNickeau
23*37748cd8SNickeau    /** @var helper_plugin_sqlite $sqlite */
24*37748cd8SNickeau    protected static $sqlite;
25*37748cd8SNickeau
26*37748cd8SNickeau    /**
27*37748cd8SNickeau     *
28*37748cd8SNickeau     * @return helper_plugin_sqlite $sqlite
29*37748cd8SNickeau     */
30*37748cd8SNickeau    public static function getSqlite()
31*37748cd8SNickeau    {
32*37748cd8SNickeau        /**
33*37748cd8SNickeau         * sqlite is stored in a static variable
34*37748cd8SNickeau         * because when we run the {@link cli_plugin_combo},
35*37748cd8SNickeau         * we will run in the error:
36*37748cd8SNickeau         * ``
37*37748cd8SNickeau         * failed to open stream: Too many open files
38*37748cd8SNickeau         * ``
39*37748cd8SNickeau         * There is by default a limit of 1024 open files
40*37748cd8SNickeau         * which means that if there is more than 1024 pages, you fail.
41*37748cd8SNickeau         *
42*37748cd8SNickeau         * In test, we are running in different context (ie different root
43*37748cd8SNickeau         * directory for DokuWiki and therefore different $conf
44*37748cd8SNickeau         * and therefore different metadir where sqlite is stored)
45*37748cd8SNickeau         * Because a sql file may be deleted, we may get:
46*37748cd8SNickeau         * ```
47*37748cd8SNickeau         * RuntimeException: HY000 8 attempt to write a readonly database:
48*37748cd8SNickeau         * ```
49*37748cd8SNickeau         * To avoid this error, we check that we are still in the same metadir
50*37748cd8SNickeau         * where the sqlite database is stored. If not, we create a new instance
51*37748cd8SNickeau         *
52*37748cd8SNickeau         */
53*37748cd8SNickeau        global $conf;
54*37748cd8SNickeau        $metaDir = $conf['metadir'];
55*37748cd8SNickeau        $init = true;
56*37748cd8SNickeau        if (self::$sqlite != null) {
57*37748cd8SNickeau            $adapter = self::$sqlite->getAdapter();
58*37748cd8SNickeau            /**
59*37748cd8SNickeau             * Adapter may be null
60*37748cd8SNickeau             * when the SQLite & PDO SQLite
61*37748cd8SNickeau             * are not installed
62*37748cd8SNickeau             * ie: SQLite & PDO SQLite support missing
63*37748cd8SNickeau             */
64*37748cd8SNickeau            if ($adapter != null) {
65*37748cd8SNickeau                $dbFile = $adapter->getDbFile();
66*37748cd8SNickeau                if (file_exists($dbFile)) {
67*37748cd8SNickeau                    if (strpos($dbFile, $metaDir) === 0) {
68*37748cd8SNickeau                        $init = false;
69*37748cd8SNickeau                    } else {
70*37748cd8SNickeau                        self::$sqlite->getAdapter()->closedb();
71*37748cd8SNickeau                        self::$sqlite = null;
72*37748cd8SNickeau                    }
73*37748cd8SNickeau                }
74*37748cd8SNickeau            }
75*37748cd8SNickeau        }
76*37748cd8SNickeau        if ($init) {
77*37748cd8SNickeau
78*37748cd8SNickeau            /**
79*37748cd8SNickeau             * Init
80*37748cd8SNickeau             */
81*37748cd8SNickeau            self::$sqlite = plugin_load('helper', 'sqlite');
82*37748cd8SNickeau            if (self::$sqlite == null) {
83*37748cd8SNickeau                # TODO: Man we cannot get the message anymore ['SqliteMandatory'];
84*37748cd8SNickeau                $sqliteMandatoryMessage = "The Sqlite Plugin is mandatory. Some functionalities of the ComboStrap Plugin may not work.";
85*37748cd8SNickeau                LogUtility::log2FrontEnd($sqliteMandatoryMessage, LogUtility::LVL_MSG_ERROR);
86*37748cd8SNickeau                return null;
87*37748cd8SNickeau            }
88*37748cd8SNickeau            $adapter = self::$sqlite->getAdapter();
89*37748cd8SNickeau            if ($adapter == null) {
90*37748cd8SNickeau                self::sendMessageAsNotAvailable();
91*37748cd8SNickeau                return null;
92*37748cd8SNickeau            }
93*37748cd8SNickeau
94*37748cd8SNickeau            $adapter->setUseNativeAlter(true);
95*37748cd8SNickeau
96*37748cd8SNickeau            // The name of the database (on windows, it should be
97*37748cd8SNickeau            $dbname = strtolower(PluginUtility::PLUGIN_BASE_NAME);
98*37748cd8SNickeau            global $conf;
99*37748cd8SNickeau
100*37748cd8SNickeau            $oldDbName = '404manager';
101*37748cd8SNickeau            $oldDbFile = $conf['metadir'] . "/{$oldDbName}.sqlite";
102*37748cd8SNickeau            $oldDbFileSqlite3 = $conf['metadir'] . "/{$oldDbName}.sqlite3";
103*37748cd8SNickeau            if (file_exists($oldDbFile) || file_exists($oldDbFileSqlite3)) {
104*37748cd8SNickeau                $dbname = $oldDbName;
105*37748cd8SNickeau            }
106*37748cd8SNickeau
107*37748cd8SNickeau            $init = self::$sqlite->init($dbname, DOKU_PLUGIN . PluginUtility::PLUGIN_BASE_NAME . '/db/');
108*37748cd8SNickeau            if (!$init) {
109*37748cd8SNickeau                # TODO: Message 'SqliteUnableToInitialize'
110*37748cd8SNickeau                $message = "Unable to initialize Sqlite";
111*37748cd8SNickeau                LogUtility::msg($message, MSG_MANAGERS_ONLY);
112*37748cd8SNickeau            } else {
113*37748cd8SNickeau                // regexp implementation
114*37748cd8SNickeau                // https://stackoverflow.com/questions/5071601/how-do-i-use-regex-in-a-sqlite-query/18484596#18484596
115*37748cd8SNickeau                $adapter = self::$sqlite->getAdapter();
116*37748cd8SNickeau                $adapter->create_function('regexp',
117*37748cd8SNickeau                    function ($pattern, $data, $delimiter = '~', $modifiers = 'isuS') {
118*37748cd8SNickeau                        if (isset($pattern, $data) === true) {
119*37748cd8SNickeau                            return (preg_match(sprintf('%1$s%2$s%1$s%3$s', $delimiter, $pattern, $modifiers), $data) > 0);
120*37748cd8SNickeau                        }
121*37748cd8SNickeau                        return null;
122*37748cd8SNickeau                    },
123*37748cd8SNickeau                    4
124*37748cd8SNickeau                );
125*37748cd8SNickeau            }
126*37748cd8SNickeau        }
127*37748cd8SNickeau        return self::$sqlite;
128*37748cd8SNickeau
129*37748cd8SNickeau    }
130*37748cd8SNickeau
131*37748cd8SNickeau    /**
132*37748cd8SNickeau     * Print debug info to the console in order to resolve
133*37748cd8SNickeau     * RuntimeException: HY000 8 attempt to write a readonly database
134*37748cd8SNickeau     * https://phpunit.readthedocs.io/en/latest/writing-tests-for-phpunit.html#error-output
135*37748cd8SNickeau     * @param helper_plugin_sqlite $sqlite
136*37748cd8SNickeau     */
137*37748cd8SNickeau    public static function printDbInfoAtConsole(helper_plugin_sqlite $sqlite)
138*37748cd8SNickeau    {
139*37748cd8SNickeau        $dbFile = $sqlite->getAdapter()->getDbFile();
140*37748cd8SNickeau        fwrite(STDERR, "Stderr DbFile: " . $dbFile . "\n");
141*37748cd8SNickeau        if (file_exists($dbFile)) {
142*37748cd8SNickeau            fwrite(STDERR, "File does exists\n");
143*37748cd8SNickeau            fwrite(STDERR, "Permission " . substr(sprintf('%o', fileperms($dbFile)), -4) . "\n");
144*37748cd8SNickeau        } else {
145*37748cd8SNickeau            fwrite(STDERR, "File does not exist\n");
146*37748cd8SNickeau        }
147*37748cd8SNickeau
148*37748cd8SNickeau        global $conf;
149*37748cd8SNickeau        $metadir = $conf['metadir'];
150*37748cd8SNickeau        fwrite(STDERR, "MetaDir: " . $metadir . "\n");
151*37748cd8SNickeau        $subdir = strpos($dbFile, $metadir) === 0;
152*37748cd8SNickeau        if ($subdir) {
153*37748cd8SNickeau            fwrite(STDERR, "Meta is a subdirectory of the db \n");
154*37748cd8SNickeau        } else {
155*37748cd8SNickeau            fwrite(STDERR, "Meta is a not subdirectory of the db \n");
156*37748cd8SNickeau        }
157*37748cd8SNickeau
158*37748cd8SNickeau    }
159*37748cd8SNickeau
160*37748cd8SNickeau    /**
161*37748cd8SNickeau     * Json support
162*37748cd8SNickeau     */
163*37748cd8SNickeau    public static function supportJson(): bool
164*37748cd8SNickeau    {
165*37748cd8SNickeau
166*37748cd8SNickeau        $sqlite = self::getSqlite();
167*37748cd8SNickeau        if ($sqlite === null) {
168*37748cd8SNickeau            self::sendMessageAsNotAvailable();
169*37748cd8SNickeau            return false;
170*37748cd8SNickeau        }
171*37748cd8SNickeau
172*37748cd8SNickeau        $res = $sqlite->query("PRAGMA compile_options");
173*37748cd8SNickeau        $isJsonEnabled = false;
174*37748cd8SNickeau        foreach ($sqlite->res2arr($res) as $row) {
175*37748cd8SNickeau            if ($row["compile_option"] === "ENABLE_JSON1") {
176*37748cd8SNickeau                $isJsonEnabled = true;
177*37748cd8SNickeau                break;
178*37748cd8SNickeau            }
179*37748cd8SNickeau        };
180*37748cd8SNickeau        $sqlite->res_close($res);
181*37748cd8SNickeau        return $isJsonEnabled;
182*37748cd8SNickeau    }
183*37748cd8SNickeau
184*37748cd8SNickeau    /**
185*37748cd8SNickeau     * @param helper_plugin_sqlite $sqlite
186*37748cd8SNickeau     * @param string $executableSql
187*37748cd8SNickeau     * @param array $parameters
188*37748cd8SNickeau     * @return bool|\PDOStatement|\SQLiteResult
189*37748cd8SNickeau     */
190*37748cd8SNickeau    public static function queryWithParameters(helper_plugin_sqlite $sqlite, string $executableSql, array $parameters)
191*37748cd8SNickeau    {
192*37748cd8SNickeau        $args = [$executableSql];
193*37748cd8SNickeau        $args = array_merge($args, $parameters);
194*37748cd8SNickeau        return $sqlite->getAdapter()->query($args);
195*37748cd8SNickeau    }
196*37748cd8SNickeau
197*37748cd8SNickeau    public static function sendMessageAsNotAvailable(): void
198*37748cd8SNickeau    {
199*37748cd8SNickeau        $sqliteMandatoryMessage = "The Sqlite Php Extension is mandatory. It seems that it's not available on this installation.";
200*37748cd8SNickeau        LogUtility::log2FrontEnd($sqliteMandatoryMessage, LogUtility::LVL_MSG_ERROR);
201*37748cd8SNickeau    }
202*37748cd8SNickeau}
203