1<?php 2 3/** 4 * @noinspection SqlNoDataSourceInspection 5 * @noinspection SqlDialectInspection 6 * @noinspection PhpComposerExtensionStubsInspection 7 */ 8 9use dokuwiki\Extension\Plugin; 10use dokuwiki\plugin\sqlite\SQLiteDB; 11use dokuwiki\plugin\sqlite\Tools; 12 13// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols, PSR1.Classes.ClassDeclaration.MultipleClasses 14// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps 15 16/** 17 * For compatibility with previous adapter implementation. 18 */ 19if (!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo'); 20class helper_plugin_sqlite_adapter_dummy 21{ 22 public function getName() 23 { 24 return DOKU_EXT_PDO; 25 } 26 27 public function setUseNativeAlter($set) 28 { 29 } 30} 31 32/** 33 * DokuWiki Plugin sqlite (Helper Component) 34 * 35 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 36 * @author Andreas Gohr <gohr@cosmocode.de> 37 * @deprecated 2023-03-15 38 */ 39class helper_plugin_sqlite extends Plugin 40{ 41 /** @var SQLiteDB|null */ 42 protected $adapter; 43 44 /** @var array result cache */ 45 protected $data; 46 47 /** 48 * constructor 49 */ 50 public function __construct() 51 { 52 if (!$this->existsPDOSqlite()) { 53 msg('PDO SQLite support missing in this PHP install - The sqlite plugin will not work', -1); 54 } 55 $this->adapter = new helper_plugin_sqlite_adapter_dummy(); 56 } 57 58 /** 59 * Get the current Adapter 60 * @return SQLiteDB|null 61 */ 62 public function getAdapter() 63 { 64 return $this->adapter; 65 } 66 67 /** 68 * Keep separate instances for every call to keep database connections 69 */ 70 public function isSingleton() 71 { 72 return false; 73 } 74 75 /** 76 * check availabilty of PHP PDO sqlite3 77 */ 78 public function existsPDOSqlite() 79 { 80 if (class_exists('pdo')) { 81 return in_array('sqlite', \PDO::getAvailableDrivers()); 82 } 83 return false; 84 } 85 86 /** 87 * Initializes and opens the database 88 * 89 * Needs to be called right after loading this helper plugin 90 * 91 * @param string $dbname 92 * @param string $updatedir - Database update infos 93 * @return bool 94 */ 95 public function init($dbname, $updatedir) 96 { 97 if (!defined('DOKU_UNITTEST')) { // for now we don't want to trigger the deprecation warning in the tests 98 dbg_deprecated(SQLiteDB::class); 99 } 100 101 try { 102 $this->adapter = new SQLiteDB($dbname, $updatedir, $this); 103 } catch (Exception $e) { 104 msg('SQLite: ' . $e->getMessage(), -1); 105 return false; 106 } 107 return true; 108 } 109 110 /** 111 * This is called from the adapter itself for backwards compatibility 112 * 113 * @param SQLiteDB $adapter 114 * @return void 115 */ 116 public function setAdapter($adapter) 117 { 118 $this->adapter = $adapter; 119 } 120 121 /** 122 * Registers a User Defined Function for use in SQL statements 123 */ 124 public function create_function($function_name, $callback, $num_args) 125 { 126 $this->adapter->getPdo()->sqliteCreateFunction($function_name, $callback, $num_args); 127 } 128 129 // region query and result handling functions 130 131 /** 132 * Convenience function to run an INSERT OR REPLACE operation 133 * 134 * The function takes a key-value array with the column names in the key and the actual value in the value, 135 * build the appropriate query and executes it. 136 * 137 * @param string $table the table the entry should be saved to (will not be escaped) 138 * @param array $entry A simple key-value pair array (only values will be escaped) 139 * @return bool 140 */ 141 public function storeEntry($table, $entry) 142 { 143 try { 144 $this->adapter->saveRecord($table, $entry); 145 } catch (\Exception $e) { 146 msg('SQLite: ' . $e->getMessage(), -1); 147 return false; 148 } 149 150 return true; 151 } 152 153 /** 154 * Execute a query with the given parameters. 155 * 156 * Takes care of escaping 157 * 158 * 159 * @param string ...$args - the arguments of query(), the first is the sql and others are values 160 */ 161 public function query() 162 { 163 // get function arguments 164 $args = func_get_args(); 165 166 // clear the cache 167 $this->data = null; 168 169 try { 170 $sql = $this->prepareSql($args); 171 return $this->adapter->query($sql); 172 } catch (\Exception $e) { 173 msg('SQLite: ' . $e->getMessage(), -1); 174 return false; 175 } 176 } 177 178 /** 179 * Prepare a query with the given arguments. 180 * 181 * Takes care of escaping 182 * 183 * @param array $args 184 * array of arguments: 185 * - string $sql - the statement 186 * - arguments... 187 * @return bool|string 188 * @throws Exception 189 */ 190 public function prepareSql($args) 191 { 192 193 $sql = trim(array_shift($args)); 194 $sql = rtrim($sql, ';'); 195 196 if (!$sql) { 197 throw new \Exception('No SQL statement given', -1); 198 } 199 200 $argc = count($args); 201 if ($argc > 0 && is_array($args[0])) { 202 $args = $args[0]; 203 $argc = count($args); 204 } 205 206 // check number of arguments 207 $qmc = substr_count($sql, '?'); 208 if ($argc < $qmc) { 209 throw new \Exception('Not enough arguments passed for statement. ' . 210 'Expected ' . $qmc . ' got ' . $argc . ' - ' . hsc($sql)); 211 } elseif ($argc > $qmc) { 212 throw new \Exception('Too much arguments passed for statement. ' . 213 'Expected ' . $qmc . ' got ' . $argc . ' - ' . hsc($sql)); 214 } 215 216 // explode at wildcard, then join again 217 $parts = explode('?', $sql, $argc + 1); 218 $args = array_map([$this->adapter->getPdo(), 'quote'], $args); 219 $sql = ''; 220 221 while (($part = array_shift($parts)) !== null) { 222 $sql .= $part; 223 $sql .= array_shift($args); 224 } 225 226 return $sql; 227 } 228 229 230 /** 231 * Closes the result set (and it's cursors) 232 * 233 * If you're doing SELECT queries inside a TRANSACTION, be sure to call this 234 * function on all your results sets, before COMMITing the transaction. 235 * 236 * Also required when not all rows of a result are fetched 237 * 238 * @param \PDOStatement $res 239 * @return bool 240 */ 241 public function res_close($res) 242 { 243 if (!$res) return false; 244 245 return $res->closeCursor(); 246 } 247 248 /** 249 * Returns a complete result set as array 250 * 251 * @param \PDOStatement $res 252 * @return array 253 */ 254 public function res2arr($res, $assoc = true) 255 { 256 if (!$res) return []; 257 258 // this is a bullshit workaround for having res2arr and res2count work on one result 259 if (!$this->data) { 260 $mode = $assoc ? PDO::FETCH_ASSOC : PDO::FETCH_NUM; 261 $this->data = $res->fetchAll($mode); 262 } 263 return $this->data; 264 } 265 266 /** 267 * Return the next row from the result set as associative array 268 * 269 * @param \PDOStatement $res 270 * @param int $rownum will be ignored 271 */ 272 public function res2row($res, $rownum = 0) 273 { 274 if (!$res) return false; 275 276 return $res->fetch(\PDO::FETCH_ASSOC); 277 } 278 279 /** 280 * Return the first value from the next row. 281 * 282 * @param \PDOStatement $res 283 * @return mixed 284 */ 285 public function res2single($res) 286 { 287 if (!$res) return false; 288 289 $data = $res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 0); 290 if (empty($data)) { 291 return false; 292 } 293 return $data[0]; 294 } 295 296 /** 297 * fetch the next row as zero indexed array 298 * 299 * @param \PDOStatement $res 300 * @return array|bool 301 */ 302 public function res_fetch_array($res) 303 { 304 if (!$res) return false; 305 306 return $res->fetch(PDO::FETCH_NUM); 307 } 308 309 /** 310 * fetch the next row as assocative array 311 * 312 * @param \PDOStatement $res 313 * @return array|bool 314 */ 315 public function res_fetch_assoc($res) 316 { 317 if (!$res) return false; 318 319 return $res->fetch(PDO::FETCH_ASSOC); 320 } 321 322 /** 323 * Count the number of records in result 324 * 325 * This function is really inperformant in PDO and should be avoided! 326 * 327 * @param \PDOStatement $res 328 * @return int 329 */ 330 public function res2count($res) 331 { 332 if (!$res) return 0; 333 334 // this is a bullshit workaround for having res2arr and res2count work on one result 335 if (!$this->data) { 336 $this->data = $this->res2arr($res); 337 } 338 339 return count($this->data); 340 } 341 342 /** 343 * Count the number of records changed last time 344 * 345 * @param \PDOStatement $res 346 * @return int 347 */ 348 public function countChanges($res) 349 { 350 if (!$res) return 0; 351 352 return $res->rowCount(); 353 } 354 355 // endregion 356 357 // region quoting/escaping functions 358 359 /** 360 * Join the given values and quote them for SQL insertion 361 */ 362 public function quote_and_join($vals, $sep = ',') 363 { 364 $vals = array_map([$this->adapter->getPdo(), 'quote'], $vals); 365 return implode($sep, $vals); 366 } 367 368 /** 369 * Quotes a string, by escaping it and adding quotes 370 */ 371 public function quote_string($string) 372 { 373 return $this->adapter->getPdo()->quote($string); 374 } 375 376 /** 377 * Similar to quote_string, but without the quotes, useful to construct LIKE patterns 378 */ 379 public function escape_string($str) 380 { 381 return trim($this->adapter->getPdo()->quote($str), "'"); 382 } 383 384 // endregion 385 386 // region speciality functions 387 388 /** 389 * Split sql queries on semicolons, unless when semicolons are quoted 390 * 391 * Usually you don't need this. It's only really needed if you need individual results for 392 * multiple queries. For example in the admin interface. 393 * 394 * @param string $sql 395 * @return array sql queries 396 * @deprecated 397 */ 398 public function SQLstring2array($sql) 399 { 400 if (!DOKU_UNITTEST) { // for now we don't want to trigger the deprecation warning in the tests 401 dbg_deprecated(Tools::class . '::SQLstring2array'); 402 } 403 return Tools::SQLstring2array($sql); 404 } 405 406 /** 407 * @deprecated needs to be fixed in stuct and structpublish 408 */ 409 public function doTransaction($sql, $sqlpreparing = true) 410 { 411 throw new \Exception( 412 'This method seems to never have done what it suggests. Please use the query() function instead.' 413 ); 414 } 415 416 // endregion 417} 418