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