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