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