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/** 14 * For compatibility with previous adapter implementation. 15 */ 16if (!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo'); 17class helper_plugin_sqlite_adapter_dummy 18{ 19 public function getName() 20 { 21 return DOKU_EXT_PDO; 22 } 23 24 public function setUseNativeAlter($set) 25 { 26 } 27} 28 29/** 30 * DokuWiki Plugin sqlite (Helper Component) 31 * 32 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 33 * @author Andreas Gohr <gohr@cosmocode.de> 34 * @deprecated 2023-03-15 35 */ 36class helper_plugin_sqlite extends Plugin 37{ 38 /** @var SQLiteDB|null */ 39 protected $adapter; 40 41 /** @var array result cache */ 42 protected $data; 43 44 /** 45 * constructor 46 */ 47 public function __construct() 48 { 49 if (!$this->existsPDOSqlite()) { 50 msg('PDO SQLite support missing in this PHP install - The sqlite plugin will not work', -1); 51 } 52 $this->adapter = new helper_plugin_sqlite_adapter_dummy(); 53 } 54 55 /** 56 * Get the current Adapter 57 * @return SQLiteDB|null 58 */ 59 public function getAdapter() 60 { 61 return $this->adapter; 62 } 63 64 /** 65 * Keep separate instances for every call to keep database connections 66 */ 67 public function isSingleton() 68 { 69 return false; 70 } 71 72 /** 73 * check availabilty of PHP PDO sqlite3 74 */ 75 public function existsPDOSqlite() 76 { 77 if (class_exists('pdo')) { 78 return in_array('sqlite', \PDO::getAvailableDrivers()); 79 } 80 return false; 81 } 82 83 /** 84 * Initializes and opens the database 85 * 86 * Needs to be called right after loading this helper plugin 87 * 88 * @param string $dbname 89 * @param string $updatedir - Database update infos 90 * @return bool 91 */ 92 public function init($dbname, $updatedir) 93 { 94 if (!defined('DOKU_UNITTEST')) { // for now we don't want to trigger the deprecation warning in the tests 95 dbg_deprecated(SQLiteDB::class); 96 } 97 98 try { 99 $this->adapter = new SQLiteDB($dbname, $updatedir, $this); 100 } catch (Exception $e) { 101 msg('SQLite: ' . $e->getMessage(), -1); 102 return false; 103 } 104 return true; 105 } 106 107 /** 108 * This is called from the adapter itself for backwards compatibility 109 * 110 * @param SQLiteDB $adapter 111 * @return void 112 */ 113 public function setAdapter($adapter) 114 { 115 $this->adapter = $adapter; 116 } 117 118 /** 119 * Registers a User Defined Function for use in SQL statements 120 */ 121 public function create_function($function_name, $callback, $num_args) 122 { 123 $this->adapter->getPdo()->sqliteCreateFunction($function_name, $callback, $num_args); 124 } 125 126 // region query and result handling functions 127 128 /** 129 * Convenience function to run an INSERT OR REPLACE operation 130 * 131 * The function takes a key-value array with the column names in the key and the actual value in the value, 132 * build the appropriate query and executes it. 133 * 134 * @param string $table the table the entry should be saved to (will not be escaped) 135 * @param array $entry A simple key-value pair array (only values will be escaped) 136 * @return bool 137 */ 138 public function storeEntry($table, $entry) 139 { 140 try { 141 $this->adapter->saveRecord($table, $entry); 142 } catch (\Exception $e) { 143 msg('SQLite: ' . $e->getMessage(), -1); 144 return false; 145 } 146 147 return true; 148 } 149 150 /** 151 * Execute a query with the given parameters. 152 * 153 * Takes care of escaping 154 * 155 * 156 * @param string ...$args - the arguments of query(), the first is the sql and others are values 157 */ 158 public function query() 159 { 160 // get function arguments 161 $args = func_get_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