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