1a1e6784eSAndreas Gohr<?php 28da7d805SAndreas Gohr 38da7d805SAndreas Gohr/** 48da7d805SAndreas Gohr * @noinspection SqlNoDataSourceInspection 58da7d805SAndreas Gohr * @noinspection SqlDialectInspection 68da7d805SAndreas Gohr * @noinspection PhpComposerExtensionStubsInspection 78da7d805SAndreas Gohr */ 88da7d805SAndreas Gohr 9a7a40fb2SAnna Dabrowskause dokuwiki\Extension\Plugin; 108da7d805SAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 118da7d805SAndreas Gohruse dokuwiki\plugin\sqlite\Tools; 128da7d805SAndreas Gohr 1314fe8e27SAnna Dabrowska// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols, PSR1.Classes.ClassDeclaration.MultipleClasses 1414fe8e27SAnna Dabrowska// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps 1514fe8e27SAnna Dabrowska 16801b921eSSzymon Olewniczak/** 17801b921eSSzymon Olewniczak * For compatibility with previous adapter implementation. 18801b921eSSzymon Olewniczak */ 19801b921eSSzymon Olewniczakif (!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo'); 20801b921eSSzymon Olewniczakclass helper_plugin_sqlite_adapter_dummy 21801b921eSSzymon Olewniczak{ 22a7a40fb2SAnna Dabrowska public function getName() 23a7a40fb2SAnna Dabrowska { 24801b921eSSzymon Olewniczak return DOKU_EXT_PDO; 25801b921eSSzymon Olewniczak } 26801b921eSSzymon Olewniczak 27a7a40fb2SAnna Dabrowska public function setUseNativeAlter($set) 28a7a40fb2SAnna Dabrowska { 29a7a40fb2SAnna Dabrowska } 30801b921eSSzymon Olewniczak} 31801b921eSSzymon Olewniczak 32a1e6784eSAndreas Gohr/** 33a1e6784eSAndreas Gohr * DokuWiki Plugin sqlite (Helper Component) 34a1e6784eSAndreas Gohr * 35a1e6784eSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 36a1e6784eSAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 378da7d805SAndreas Gohr * @deprecated 2023-03-15 38a1e6784eSAndreas Gohr */ 39a7a40fb2SAnna Dabrowskaclass helper_plugin_sqlite extends Plugin 408da7d805SAndreas Gohr{ 418da7d805SAndreas Gohr /** @var SQLiteDB|null */ 42a7a40fb2SAnna Dabrowska protected $adapter; 43aa81d781SKlap-in 448da7d805SAndreas Gohr /** @var array result cache */ 458da7d805SAndreas Gohr protected $data; 468da7d805SAndreas Gohr 473e9ac593SGerrit Uitslag /** 488da7d805SAndreas Gohr * constructor 493e9ac593SGerrit Uitslag */ 508da7d805SAndreas Gohr public function __construct() 518da7d805SAndreas Gohr { 528da7d805SAndreas Gohr if (!$this->existsPDOSqlite()) { 538da7d805SAndreas Gohr msg('PDO SQLite support missing in this PHP install - The sqlite plugin will not work', -1); 548da7d805SAndreas Gohr } 55801b921eSSzymon Olewniczak $this->adapter = new helper_plugin_sqlite_adapter_dummy(); 568da7d805SAndreas Gohr } 578da7d805SAndreas Gohr 588da7d805SAndreas Gohr /** 598da7d805SAndreas Gohr * Get the current Adapter 608da7d805SAndreas Gohr * @return SQLiteDB|null 618da7d805SAndreas Gohr */ 628da7d805SAndreas Gohr public function getAdapter() 638da7d805SAndreas Gohr { 64ca2f2adaSKlap-in return $this->adapter; 65ca2f2adaSKlap-in } 66ca2f2adaSKlap-in 67a1e6784eSAndreas Gohr /** 68aa81d781SKlap-in * Keep separate instances for every call to keep database connections 69aa81d781SKlap-in */ 708da7d805SAndreas Gohr public function isSingleton() 718da7d805SAndreas Gohr { 72aa81d781SKlap-in return false; 73aa81d781SKlap-in } 74aa81d781SKlap-in 75aa81d781SKlap-in /** 76aa81d781SKlap-in * check availabilty of PHP PDO sqlite3 77aa81d781SKlap-in */ 788da7d805SAndreas Gohr public function existsPDOSqlite() 798da7d805SAndreas Gohr { 8087fa2c18Sstretchyboy if (class_exists('pdo')) { 818da7d805SAndreas Gohr return in_array('sqlite', \PDO::getAvailableDrivers()); 827ed6069fSAdrian Lang } 83aa81d781SKlap-in return false; 84a1e6784eSAndreas Gohr } 85a1e6784eSAndreas Gohr 86a1e6784eSAndreas Gohr /** 87a1e6784eSAndreas Gohr * Initializes and opens the database 88a1e6784eSAndreas Gohr * 89a1e6784eSAndreas Gohr * Needs to be called right after loading this helper plugin 90aa81d781SKlap-in * 91aa81d781SKlap-in * @param string $dbname 92aa81d781SKlap-in * @param string $updatedir - Database update infos 93aa81d781SKlap-in * @return bool 94a1e6784eSAndreas Gohr */ 958da7d805SAndreas Gohr public function init($dbname, $updatedir) 968da7d805SAndreas Gohr { 97801b921eSSzymon Olewniczak if (!defined('DOKU_UNITTEST')) { // for now we don't want to trigger the deprecation warning in the tests 983a56750bSAndreas Gohr dbg_deprecated(SQLiteDB::class); 99d0a5ba7aSAndreas Gohr } 1003a56750bSAndreas Gohr 1018da7d805SAndreas Gohr try { 1028da7d805SAndreas Gohr $this->adapter = new SQLiteDB($dbname, $updatedir, $this); 1038da7d805SAndreas Gohr } catch (Exception $e) { 1048da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 105c137e95fSAndreas Gohr return false; 106c137e95fSAndreas Gohr } 107a1e6784eSAndreas Gohr return true; 108a1e6784eSAndreas Gohr } 109a1e6784eSAndreas Gohr 110a1e6784eSAndreas Gohr /** 1118da7d805SAndreas Gohr * This is called from the adapter itself for backwards compatibility 1128da7d805SAndreas Gohr * 1138da7d805SAndreas Gohr * @param SQLiteDB $adapter 1148da7d805SAndreas Gohr * @return void 115a1e6784eSAndreas Gohr */ 116a7a40fb2SAnna Dabrowska public function setAdapter($adapter) 1178da7d805SAndreas Gohr { 1188da7d805SAndreas Gohr $this->adapter = $adapter; 119a34ef333SKlap-in } 120f10ea6c1SKlap-in 121a1e6784eSAndreas Gohr /** 1223ae3f79eSKlap-in * Registers a User Defined Function for use in SQL statements 1233ae3f79eSKlap-in */ 1248da7d805SAndreas Gohr public function create_function($function_name, $callback, $num_args) 1258da7d805SAndreas Gohr { 12610cb854aSAndreas Gohr $this->adapter->getPdo()->sqliteCreateFunction($function_name, $callback, $num_args); 1273ae3f79eSKlap-in } 1283ae3f79eSKlap-in 1298da7d805SAndreas Gohr // region query and result handling functions 1308da7d805SAndreas Gohr 1313ae3f79eSKlap-in /** 132e7b0736cSAndreas Gohr * Convenience function to run an INSERT OR REPLACE operation 133e7b0736cSAndreas Gohr * 134e7b0736cSAndreas Gohr * The function takes a key-value array with the column names in the key and the actual value in the value, 135e7b0736cSAndreas Gohr * build the appropriate query and executes it. 136e7b0736cSAndreas Gohr * 137e7b0736cSAndreas Gohr * @param string $table the table the entry should be saved to (will not be escaped) 138e7b0736cSAndreas Gohr * @param array $entry A simple key-value pair array (only values will be escaped) 1398da7d805SAndreas Gohr * @return bool 140e7b0736cSAndreas Gohr */ 1418da7d805SAndreas Gohr public function storeEntry($table, $entry) 1428da7d805SAndreas Gohr { 1438da7d805SAndreas Gohr try { 1448da7d805SAndreas Gohr $this->adapter->saveRecord($table, $entry); 1458da7d805SAndreas Gohr } catch (\Exception $e) { 1468da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 1478da7d805SAndreas Gohr return false; 148e7b0736cSAndreas Gohr } 149e7b0736cSAndreas Gohr 1508da7d805SAndreas Gohr return true; 1518da7d805SAndreas Gohr } 152e7b0736cSAndreas Gohr 153e7b0736cSAndreas Gohr /** 154a1e6784eSAndreas Gohr * Execute a query with the given parameters. 155a1e6784eSAndreas Gohr * 156a1e6784eSAndreas Gohr * Takes care of escaping 157a1e6784eSAndreas Gohr * 158a2a82480SAndreas Gohr * 159a2a82480SAndreas Gohr * @param string ...$args - the arguments of query(), the first is the sql and others are values 160a1e6784eSAndreas Gohr */ 161*e062c247Ssplitbrain public function query(...$args) 1628da7d805SAndreas Gohr { 1636917135cSSzymon Olewniczak // clear the cache 1646917135cSSzymon Olewniczak $this->data = null; 1656917135cSSzymon Olewniczak 1668da7d805SAndreas Gohr try { 1671973f122SSzymon Olewniczak $sql = $this->prepareSql($args); 1681973f122SSzymon Olewniczak return $this->adapter->query($sql); 1698da7d805SAndreas Gohr } catch (\Exception $e) { 1708da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 1718da7d805SAndreas Gohr return false; 1728da7d805SAndreas Gohr } 17387fa2c18Sstretchyboy } 174a1e6784eSAndreas Gohr 1751973f122SSzymon Olewniczak /** 1761973f122SSzymon Olewniczak * Prepare a query with the given arguments. 1771973f122SSzymon Olewniczak * 1781973f122SSzymon Olewniczak * Takes care of escaping 1791973f122SSzymon Olewniczak * 1801973f122SSzymon Olewniczak * @param array $args 1811973f122SSzymon Olewniczak * array of arguments: 1821973f122SSzymon Olewniczak * - string $sql - the statement 1831973f122SSzymon Olewniczak * - arguments... 1841973f122SSzymon Olewniczak * @return bool|string 1851973f122SSzymon Olewniczak * @throws Exception 1861973f122SSzymon Olewniczak */ 187a7a40fb2SAnna Dabrowska public function prepareSql($args) 188a7a40fb2SAnna Dabrowska { 1891973f122SSzymon Olewniczak 1901973f122SSzymon Olewniczak $sql = trim(array_shift($args)); 1911973f122SSzymon Olewniczak $sql = rtrim($sql, ';'); 1921973f122SSzymon Olewniczak 1931973f122SSzymon Olewniczak if (!$sql) { 1941973f122SSzymon Olewniczak throw new \Exception('No SQL statement given', -1); 1951973f122SSzymon Olewniczak } 1961973f122SSzymon Olewniczak 1971973f122SSzymon Olewniczak $argc = count($args); 1981973f122SSzymon Olewniczak if ($argc > 0 && is_array($args[0])) { 1991973f122SSzymon Olewniczak $args = $args[0]; 2001973f122SSzymon Olewniczak $argc = count($args); 2011973f122SSzymon Olewniczak } 2021973f122SSzymon Olewniczak 2031973f122SSzymon Olewniczak // check number of arguments 2041973f122SSzymon Olewniczak $qmc = substr_count($sql, '?'); 2051973f122SSzymon Olewniczak if ($argc < $qmc) { 2061973f122SSzymon Olewniczak throw new \Exception('Not enough arguments passed for statement. ' . 2071973f122SSzymon Olewniczak 'Expected ' . $qmc . ' got ' . $argc . ' - ' . hsc($sql)); 2081973f122SSzymon Olewniczak } elseif ($argc > $qmc) { 2091973f122SSzymon Olewniczak throw new \Exception('Too much arguments passed for statement. ' . 2101973f122SSzymon Olewniczak 'Expected ' . $qmc . ' got ' . $argc . ' - ' . hsc($sql)); 2111973f122SSzymon Olewniczak } 2121973f122SSzymon Olewniczak 2131973f122SSzymon Olewniczak // explode at wildcard, then join again 2141973f122SSzymon Olewniczak $parts = explode('?', $sql, $argc + 1); 21510cb854aSAndreas Gohr $args = array_map([$this->adapter->getPdo(), 'quote'], $args); 2161973f122SSzymon Olewniczak $sql = ''; 2171973f122SSzymon Olewniczak 2181973f122SSzymon Olewniczak while (($part = array_shift($parts)) !== null) { 2191973f122SSzymon Olewniczak $sql .= $part; 2201973f122SSzymon Olewniczak $sql .= array_shift($args); 2211973f122SSzymon Olewniczak } 2221973f122SSzymon Olewniczak 2231973f122SSzymon Olewniczak return $sql; 2241973f122SSzymon Olewniczak } 2251973f122SSzymon Olewniczak 226ff97cc8fSstretchyboy 227ff97cc8fSstretchyboy /** 228b122d121SAndreas Gohr * Closes the result set (and it's cursors) 229b122d121SAndreas Gohr * 230b122d121SAndreas Gohr * If you're doing SELECT queries inside a TRANSACTION, be sure to call this 231b122d121SAndreas Gohr * function on all your results sets, before COMMITing the transaction. 232b122d121SAndreas Gohr * 2334b4b2db0SGerrit Uitslag * Also required when not all rows of a result are fetched 2344b4b2db0SGerrit Uitslag * 2358da7d805SAndreas Gohr * @param \PDOStatement $res 236b122d121SAndreas Gohr * @return bool 237b122d121SAndreas Gohr */ 2388da7d805SAndreas Gohr public function res_close($res) 2398da7d805SAndreas Gohr { 2408da7d805SAndreas Gohr if (!$res) return false; 2418da7d805SAndreas Gohr 2428da7d805SAndreas Gohr return $res->closeCursor(); 243b122d121SAndreas Gohr } 244b122d121SAndreas Gohr 245b122d121SAndreas Gohr /** 246aa81d781SKlap-in * Returns a complete result set as array 2478da7d805SAndreas Gohr * 2488da7d805SAndreas Gohr * @param \PDOStatement $res 2498da7d805SAndreas Gohr * @return array 250ff97cc8fSstretchyboy */ 2518da7d805SAndreas Gohr public function res2arr($res, $assoc = true) 2528da7d805SAndreas Gohr { 2538da7d805SAndreas Gohr if (!$res) return []; 2548da7d805SAndreas Gohr 2558da7d805SAndreas Gohr // this is a bullshit workaround for having res2arr and res2count work on one result 2568da7d805SAndreas Gohr if (!$this->data) { 2578da7d805SAndreas Gohr $mode = $assoc ? PDO::FETCH_ASSOC : PDO::FETCH_NUM; 2588da7d805SAndreas Gohr $this->data = $res->fetchAll($mode); 2598da7d805SAndreas Gohr } 2608da7d805SAndreas Gohr return $this->data; 261b5b947d7SAndreas Gohr } 262b5b947d7SAndreas Gohr 263b5b947d7SAndreas Gohr /** 2648da7d805SAndreas Gohr * Return the next row from the result set as associative array 2658da7d805SAndreas Gohr * 2668da7d805SAndreas Gohr * @param \PDOStatement $res 2678da7d805SAndreas Gohr * @param int $rownum will be ignored 268b5b947d7SAndreas Gohr */ 2698da7d805SAndreas Gohr public function res2row($res, $rownum = 0) 2708da7d805SAndreas Gohr { 2718da7d805SAndreas Gohr if (!$res) return false; 2728da7d805SAndreas Gohr 2738da7d805SAndreas Gohr return $res->fetch(\PDO::FETCH_ASSOC); 274b5b947d7SAndreas Gohr } 275b5b947d7SAndreas Gohr 276e7112ccbSAdrian Lang /** 27744685fc6SKlap-in * Return the first value from the next row. 2788da7d805SAndreas Gohr * 2798da7d805SAndreas Gohr * @param \PDOStatement $res 2808da7d805SAndreas Gohr * @return mixed 281e7112ccbSAdrian Lang */ 2828da7d805SAndreas Gohr public function res2single($res) 2838da7d805SAndreas Gohr { 2848da7d805SAndreas Gohr if (!$res) return false; 2858da7d805SAndreas Gohr 2868da7d805SAndreas Gohr $data = $res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 0); 2878da7d805SAndreas Gohr if (empty($data)) { 2888da7d805SAndreas Gohr return false; 2898da7d805SAndreas Gohr } 2908da7d805SAndreas Gohr return $data[0]; 291e7112ccbSAdrian Lang } 292fee3b689Sstretchyboy 293fee3b689Sstretchyboy /** 294fee3b689Sstretchyboy * fetch the next row as zero indexed array 2958da7d805SAndreas Gohr * 2968da7d805SAndreas Gohr * @param \PDOStatement $res 2978da7d805SAndreas Gohr * @return array|bool 298fee3b689Sstretchyboy */ 2998da7d805SAndreas Gohr public function res_fetch_array($res) 3008da7d805SAndreas Gohr { 3018da7d805SAndreas Gohr if (!$res) return false; 3028da7d805SAndreas Gohr 3038da7d805SAndreas Gohr return $res->fetch(PDO::FETCH_NUM); 30487fa2c18Sstretchyboy } 305fee3b689Sstretchyboy 306fee3b689Sstretchyboy /** 307fee3b689Sstretchyboy * fetch the next row as assocative array 3088da7d805SAndreas Gohr * 3098da7d805SAndreas Gohr * @param \PDOStatement $res 3108da7d805SAndreas Gohr * @return array|bool 311fee3b689Sstretchyboy */ 3128da7d805SAndreas Gohr public function res_fetch_assoc($res) 3138da7d805SAndreas Gohr { 3148da7d805SAndreas Gohr if (!$res) return false; 3158da7d805SAndreas Gohr 3168da7d805SAndreas Gohr return $res->fetch(PDO::FETCH_ASSOC); 317fee3b689Sstretchyboy } 318fee3b689Sstretchyboy 319fee3b689Sstretchyboy /** 32078977d74SKlap-in * Count the number of records in result 3213157674bSAndreas Gohr * 322db58e525SKlap-in * This function is really inperformant in PDO and should be avoided! 3238da7d805SAndreas Gohr * 3248da7d805SAndreas Gohr * @param \PDOStatement $res 3258da7d805SAndreas Gohr * @return int 326fee3b689Sstretchyboy */ 3278da7d805SAndreas Gohr public function res2count($res) 3288da7d805SAndreas Gohr { 3298da7d805SAndreas Gohr if (!$res) return 0; 3308da7d805SAndreas Gohr 3318da7d805SAndreas Gohr // this is a bullshit workaround for having res2arr and res2count work on one result 3328da7d805SAndreas Gohr if (!$this->data) { 3338da7d805SAndreas Gohr $this->data = $this->res2arr($res); 3348da7d805SAndreas Gohr } 3358da7d805SAndreas Gohr 3368da7d805SAndreas Gohr return count($this->data); 337fee3b689Sstretchyboy } 33824a03f6cSstretchyboy 33924a03f6cSstretchyboy /** 34024a03f6cSstretchyboy * Count the number of records changed last time 3418da7d805SAndreas Gohr * 3428da7d805SAndreas Gohr * @param \PDOStatement $res 3438da7d805SAndreas Gohr * @return int 34424a03f6cSstretchyboy */ 3458da7d805SAndreas Gohr public function countChanges($res) 3468da7d805SAndreas Gohr { 3478da7d805SAndreas Gohr if (!$res) return 0; 3488da7d805SAndreas Gohr 3498da7d805SAndreas Gohr return $res->rowCount(); 350a1e6784eSAndreas Gohr } 351a1e6784eSAndreas Gohr 3528da7d805SAndreas Gohr // endregion 3538da7d805SAndreas Gohr 3548da7d805SAndreas Gohr // region quoting/escaping functions 3558da7d805SAndreas Gohr 3568da7d805SAndreas Gohr /** 3578da7d805SAndreas Gohr * Join the given values and quote them for SQL insertion 3588da7d805SAndreas Gohr */ 3598da7d805SAndreas Gohr public function quote_and_join($vals, $sep = ',') 3608da7d805SAndreas Gohr { 36110cb854aSAndreas Gohr $vals = array_map([$this->adapter->getPdo(), 'quote'], $vals); 362a7a40fb2SAnna Dabrowska return implode($sep, $vals); 3638da7d805SAndreas Gohr } 3648da7d805SAndreas Gohr 3658da7d805SAndreas Gohr /** 3668da7d805SAndreas Gohr * Quotes a string, by escaping it and adding quotes 3678da7d805SAndreas Gohr */ 3688da7d805SAndreas Gohr public function quote_string($string) 3698da7d805SAndreas Gohr { 37010cb854aSAndreas Gohr return $this->adapter->getPdo()->quote($string); 3718da7d805SAndreas Gohr } 3728da7d805SAndreas Gohr 3738da7d805SAndreas Gohr /** 3748da7d805SAndreas Gohr * Similar to quote_string, but without the quotes, useful to construct LIKE patterns 3758da7d805SAndreas Gohr */ 3768da7d805SAndreas Gohr public function escape_string($str) 3778da7d805SAndreas Gohr { 37810cb854aSAndreas Gohr return trim($this->adapter->getPdo()->quote($str), "'"); 3798da7d805SAndreas Gohr } 3808da7d805SAndreas Gohr 3818da7d805SAndreas Gohr // endregion 3828da7d805SAndreas Gohr 3838da7d805SAndreas Gohr // region speciality functions 3848da7d805SAndreas Gohr 3858da7d805SAndreas Gohr /** 3868da7d805SAndreas Gohr * Split sql queries on semicolons, unless when semicolons are quoted 3878da7d805SAndreas Gohr * 3888da7d805SAndreas Gohr * Usually you don't need this. It's only really needed if you need individual results for 3898da7d805SAndreas Gohr * multiple queries. For example in the admin interface. 3908da7d805SAndreas Gohr * 3918da7d805SAndreas Gohr * @param string $sql 3928da7d805SAndreas Gohr * @return array sql queries 3938da7d805SAndreas Gohr * @deprecated 3948da7d805SAndreas Gohr */ 3958da7d805SAndreas Gohr public function SQLstring2array($sql) 3968da7d805SAndreas Gohr { 397d0a5ba7aSAndreas Gohr if (!DOKU_UNITTEST) { // for now we don't want to trigger the deprecation warning in the tests 3988da7d805SAndreas Gohr dbg_deprecated(Tools::class . '::SQLstring2array'); 399d0a5ba7aSAndreas Gohr } 4008da7d805SAndreas Gohr return Tools::SQLstring2array($sql); 4018da7d805SAndreas Gohr } 4028da7d805SAndreas Gohr 4038da7d805SAndreas Gohr /** 4048da7d805SAndreas Gohr * @deprecated needs to be fixed in stuct and structpublish 4058da7d805SAndreas Gohr */ 406a7a40fb2SAnna Dabrowska public function doTransaction($sql, $sqlpreparing = true) 407a7a40fb2SAnna Dabrowska { 4088da7d805SAndreas Gohr throw new \Exception( 4098da7d805SAndreas Gohr 'This method seems to never have done what it suggests. Please use the query() function instead.' 4108da7d805SAndreas Gohr ); 4118da7d805SAndreas Gohr } 4128da7d805SAndreas Gohr 4138da7d805SAndreas Gohr // endregion 414aa81d781SKlap-in} 415