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(...$args) 162 { 163 // clear the cache 164 $this->data = null; 165 166 try { 167 $sql = $this->prepareSql($args); 168 return $this->adapter->query($sql); 169 } catch (\Exception $e) { 170 msg('SQLite: ' . $e->getMessage(), -1); 171 return false; 172 } 173 } 174 175 /** 176 * Prepare a query with the given arguments. 177 * 178 * Takes care of escaping 179 * 180 * @param array $args 181 * array of arguments: 182 * - string $sql - the statement 183 * - arguments... 184 * @return bool|string 185 * @throws Exception 186 */ 187 public function prepareSql($args) 188 { 189 190 $sql = trim(array_shift($args)); 191 $sql = rtrim($sql, ';'); 192 193 if (!$sql) { 194 throw new \Exception('No SQL statement given', -1); 195 } 196 197 $argc = count($args); 198 if ($argc > 0 && is_array($args[0])) { 199 $args = $args[0]; 200 $argc = count($args); 201 } 202 203 // check number of arguments 204 $qmc = substr_count($sql, '?'); 205 if ($argc < $qmc) { 206 throw new \Exception('Not enough arguments passed for statement. ' . 207 'Expected ' . $qmc . ' got ' . $argc . ' - ' . hsc($sql)); 208 } elseif ($argc > $qmc) { 209 throw new \Exception('Too much arguments passed for statement. ' . 210 'Expected ' . $qmc . ' got ' . $argc . ' - ' . hsc($sql)); 211 } 212 213 // explode at wildcard, then join again 214 $parts = explode('?', $sql, $argc + 1); 215 $args = array_map([$this->adapter->getPdo(), 'quote'], $args); 216 $sql = ''; 217 218 while (($part = array_shift($parts)) !== null) { 219 $sql .= $part; 220 $sql .= array_shift($args); 221 } 222 223 return $sql; 224 } 225 226 227 /** 228 * Closes the result set (and it's cursors) 229 * 230 * If you're doing SELECT queries inside a TRANSACTION, be sure to call this 231 * function on all your results sets, before COMMITing the transaction. 232 * 233 * Also required when not all rows of a result are fetched 234 * 235 * @param \PDOStatement $res 236 * @return bool 237 */ 238 public function res_close($res) 239 { 240 if (!$res) return false; 241 242 return $res->closeCursor(); 243 } 244 245 /** 246 * Returns a complete result set as array 247 * 248 * @param \PDOStatement $res 249 * @return array 250 */ 251 public function res2arr($res, $assoc = true) 252 { 253 if (!$res) return []; 254 255 // this is a bullshit workaround for having res2arr and res2count work on one result 256 if (!$this->data) { 257 $mode = $assoc ? PDO::FETCH_ASSOC : PDO::FETCH_NUM; 258 $this->data = $res->fetchAll($mode); 259 } 260 return $this->data; 261 } 262 263 /** 264 * Return the next row from the result set as associative array 265 * 266 * @param \PDOStatement $res 267 * @param int $rownum will be ignored 268 */ 269 public function res2row($res, $rownum = 0) 270 { 271 if (!$res) return false; 272 273 return $res->fetch(\PDO::FETCH_ASSOC); 274 } 275 276 /** 277 * Return the first value from the next row. 278 * 279 * @param \PDOStatement $res 280 * @return mixed 281 */ 282 public function res2single($res) 283 { 284 if (!$res) return false; 285 286 $data = $res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 0); 287 if (empty($data)) { 288 return false; 289 } 290 return $data[0]; 291 } 292 293 /** 294 * fetch the next row as zero indexed array 295 * 296 * @param \PDOStatement $res 297 * @return array|bool 298 */ 299 public function res_fetch_array($res) 300 { 301 if (!$res) return false; 302 303 return $res->fetch(PDO::FETCH_NUM); 304 } 305 306 /** 307 * fetch the next row as assocative array 308 * 309 * @param \PDOStatement $res 310 * @return array|bool 311 */ 312 public function res_fetch_assoc($res) 313 { 314 if (!$res) return false; 315 316 return $res->fetch(PDO::FETCH_ASSOC); 317 } 318 319 /** 320 * Count the number of records in result 321 * 322 * This function is really inperformant in PDO and should be avoided! 323 * 324 * @param \PDOStatement $res 325 * @return int 326 */ 327 public function res2count($res) 328 { 329 if (!$res) return 0; 330 331 // this is a bullshit workaround for having res2arr and res2count work on one result 332 if (!$this->data) { 333 $this->data = $this->res2arr($res); 334 } 335 336 return count($this->data); 337 } 338 339 /** 340 * Count the number of records changed last time 341 * 342 * @param \PDOStatement $res 343 * @return int 344 */ 345 public function countChanges($res) 346 { 347 if (!$res) return 0; 348 349 return $res->rowCount(); 350 } 351 352 // endregion 353 354 // region quoting/escaping functions 355 356 /** 357 * Join the given values and quote them for SQL insertion 358 */ 359 public function quote_and_join($vals, $sep = ',') 360 { 361 $vals = array_map([$this->adapter->getPdo(), 'quote'], $vals); 362 return implode($sep, $vals); 363 } 364 365 /** 366 * Quotes a string, by escaping it and adding quotes 367 */ 368 public function quote_string($string) 369 { 370 return $this->adapter->getPdo()->quote($string); 371 } 372 373 /** 374 * Similar to quote_string, but without the quotes, useful to construct LIKE patterns 375 */ 376 public function escape_string($str) 377 { 378 return trim($this->adapter->getPdo()->quote($str), "'"); 379 } 380 381 // endregion 382 383 // region speciality functions 384 385 /** 386 * Split sql queries on semicolons, unless when semicolons are quoted 387 * 388 * Usually you don't need this. It's only really needed if you need individual results for 389 * multiple queries. For example in the admin interface. 390 * 391 * @param string $sql 392 * @return array sql queries 393 * @deprecated 394 */ 395 public function SQLstring2array($sql) 396 { 397 if (!DOKU_UNITTEST) { // for now we don't want to trigger the deprecation warning in the tests 398 dbg_deprecated(Tools::class . '::SQLstring2array'); 399 } 400 return Tools::SQLstring2array($sql); 401 } 402 403 /** 404 * @deprecated needs to be fixed in stuct and structpublish 405 */ 406 public function doTransaction($sql, $sqlpreparing = true) 407 { 408 throw new \Exception( 409 'This method seems to never have done what it suggests. Please use the query() function instead.' 410 ); 411 } 412 413 // endregion 414} 415