1<?php 2/** 3 * DokuWiki Plugin sqlite (Helper Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr <gohr@cosmocode.de> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12if(!defined('DOKU_LF')) define('DOKU_LF', "\n"); 13if(!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 14if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 15 16if(!defined('DOKU_EXT_SQLITE')) define('DOKU_EXT_SQLITE', 'sqlite'); 17if(!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo'); 18 19require_once(DOKU_PLUGIN.'sqlite/classes/adapter.php'); 20 21class helper_plugin_sqlite extends DokuWiki_Plugin { 22 var $adapter = null; 23 24 public function getInfo() { 25 return confToHash(dirname(__FILE__).'plugin.info.txt'); 26 } 27 28 /** 29 * Keep separate instances for every call to keep database connections 30 */ 31 public function isSingleton() { 32 return false; 33 } 34 35 /** 36 * constructor 37 */ 38 public function helper_plugin_sqlite() { 39 40 if(!$this->adapter) { 41 if($this->existsPDOSqlite()) { 42 require_once(DOKU_PLUGIN.'sqlite/classes/adapter_pdosqlite.php'); 43 $this->adapter = new helper_plugin_sqlite_adapter_pdosqlite(); 44 } 45 } 46 47 if(!$this->adapter) { 48 if($this->existsSqlite2()) { 49 require_once(DOKU_PLUGIN.'sqlite/classes/adapter_sqlite2.php'); 50 $this->adapter = new helper_plugin_sqlite_adapter_sqlite2(); 51 } 52 } 53 54 if(!$this->adapter) { 55 msg('SQLite & PDO SQLite support missing in this PHP install - plugin will not work', -1); 56 } 57 } 58 59 /** 60 * check availabilty of PHPs sqlite extension (for sqlite2 support) 61 */ 62 public function existsSqlite2() { 63 if(!extension_loaded('sqlite')) { 64 $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : ''; 65 if(function_exists('dl')) @dl($prefix.'sqlite.'.PHP_SHLIB_SUFFIX); 66 } 67 68 return function_exists('sqlite_open'); 69 } 70 71 /** 72 * check availabilty of PHP PDO sqlite3 73 */ 74 public function existsPDOSqlite() { 75 if(!extension_loaded('pdo_sqlite')) { 76 $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : ''; 77 if(function_exists('dl')) @dl($prefix.'pdo_sqlite.'.PHP_SHLIB_SUFFIX); 78 } 79 80 if(class_exists('pdo')) { 81 foreach(PDO::getAvailableDrivers() as $driver) { 82 if($driver == 'sqlite') { 83 return true; 84 } 85 } 86 } 87 return false; 88 } 89 90 /** 91 * Initializes and opens the database 92 * 93 * Needs to be called right after loading this helper plugin 94 * 95 * @param string $dbname 96 * @param string $updatedir - Database update infos 97 * @return bool 98 */ 99 public function init($dbname, $updatedir) { 100 101 $init = null; // set by initdb() 102 if(!$this->adapter OR !$this->adapter->initdb($dbname, $init)) return false; 103 104 return $this->_updatedb($init, $updatedir); 105 } 106 107 /** 108 * Return the current Database Version 109 */ 110 private function _currentDBversion() { 111 $sql = "SELECT val FROM opts WHERE opt = 'dbversion';"; 112 $res = $this->query($sql); 113 if(!$res) return false; 114 $row = $this->res2row($res, 0); 115 return (int) $row['val']; 116 } 117 118 /** 119 * Update the database if needed 120 * 121 * @param bool $init - true if this is a new database to initialize 122 * @param string $updatedir - Database update infos 123 * @return bool 124 */ 125 private function _updatedb($init, $updatedir) { 126 if($init) { 127 128 $current = 0; 129 } else { 130 $current = $this->_currentDBversion(); 131 if(!$current) { 132 msg("SQLite: no DB version found. '".$this->adapter->getDbname()."' DB probably broken.", -1); 133 return false; 134 } 135 } 136 137 // in case of init, add versioning table 138 if($init) { 139 if(!$this->_runupdatefile(dirname(__FILE__).'/db.sql', 0)) { 140 msg("SQLite: '".$this->adapter->getDbname()."' database upgrade failed for version ", -1); 141 return false; 142 } 143 } 144 145 $latest = (int) trim(io_readFile($updatedir.'/latest.version')); 146 147 // all up to date? 148 if($current >= $latest) return true; 149 for($i = $current + 1; $i <= $latest; $i++) { 150 $file = sprintf($updatedir.'/update%04d.sql', $i); 151 if(file_exists($file)) { 152 if(!$this->_runupdatefile($file, $i)) { 153 msg("SQLite: '".$this->adapter->getDbname()."' database upgrade failed for version ".$i, -1); 154 return false; 155 } 156 } 157 } 158 return true; 159 } 160 161 /** 162 * Updates the database structure using the given file to 163 * the given version. 164 */ 165 private function _runupdatefile($file, $version) { 166 $sql = io_readFile($file, false); 167 168 $sql = $this->SQLstring2array($sql); 169 array_unshift($sql, 'BEGIN TRANSACTION'); 170 array_push($sql, "INSERT OR REPLACE INTO opts (val,opt) VALUES ($version,'dbversion')"); 171 array_push($sql, "COMMIT TRANSACTION"); 172 173 if(!$this->doTransaction($sql)) { 174 return false; 175 } 176 return ($version == $this->_currentDBversion()); 177 } 178 179 /** 180 * Split sql queries on semicolons, unless when semicolons are quoted 181 * 182 * @param string $sql 183 * @return array sql queries 184 */ 185 public function SQLstring2array($sql) { 186 return preg_split("/;(?=([^']*'[^']*')*[^']*$)/", $sql); 187 } 188 189 /** 190 * @param array $sql queries without terminating semicolon 191 * @param bool $sqlpreparing 192 * @return bool 193 */ 194 public function doTransaction($sql, $sqlpreparing = true) { 195 foreach($sql as $s) { 196 $s = preg_replace('!^\s*--.*$!m', '', $s); 197 $s = trim($s); 198 if(!$s) continue; 199 200 if($sqlpreparing) { 201 $res = $this->query("$s;"); 202 } else { 203 $res = $this->adapter->executeQuery("$s;"); 204 } 205 if($res === false) { 206 //TODO check rollback for sqlite PDO 207 if($this->adapter->getName() == DOKU_EXT_SQLITE) { 208 $this->query('ROLLBACK TRANSACTION'); 209 } 210 return false; 211 } 212 } 213 return true; 214 } 215 216 217 218 /** 219 * Registers a User Defined Function for use in SQL statements 220 */ 221 public function create_function($function_name, $callback, $num_args) { 222 $this->adapter->create_function($function_name, $callback, $num_args); 223 } 224 225 /** 226 * Execute a query with the given parameters. 227 * 228 * Takes care of escaping 229 * 230 * @internal param string $sql - the statement 231 * @internal param $arguments ... 232 * @return bool|\SQLiteResult 233 */ 234 public function query() { 235 // get function arguments 236 $args = func_get_args(); 237 238 return $this->adapter->query($args); 239 } 240 241 /** 242 * Join the given values and quote them for SQL insertion 243 */ 244 public function quote_and_join($vals, $sep = ',') { 245 return $this->adapter->quote_and_join($vals, $sep); 246 } 247 248 /** 249 * Run sqlite_escape_string() on the given string and surround it 250 * with quotes 251 */ 252 public function quote_string($string) { 253 return $this->adapter->quote_string($string); 254 } 255 256 /** 257 * Escape string for sql 258 */ 259 public function escape_string($str) { 260 return $this->adapter->escape_string($str); 261 } 262 263 /** 264 * Returns a complete result set as array 265 */ 266 public function res2arr($res, $assoc = true) { 267 return $this->adapter->res2arr($res, $assoc); 268 } 269 270 /** 271 * Return the wanted row from a given result set as 272 * associative array 273 */ 274 public function res2row($res, $rownum = 0) { 275 return $this->adapter->res2row($res, $rownum); 276 } 277 278 /** 279 * Return the first value from the first row. 280 */ 281 public function res2single($res) { 282 return $this->adapter->res2single($res); 283 } 284 285 /** 286 * fetch the next row as zero indexed array 287 */ 288 public function res_fetch_array($res) { 289 return $this->adapter->res_fetch_array($res); 290 } 291 292 /** 293 * fetch the next row as assocative array 294 */ 295 public function res_fetch_assoc($res) { 296 return $this->adapter->res_fetch_assoc($res); 297 } 298 299 /** 300 * Count the number of records in result 301 * 302 * This function is really inperformant in PDO and should be avoided! 303 */ 304 public function res2count($res) { 305 return $this->adapter->res2count($res); 306 } 307 308 /** 309 * Count the number of records changed last time 310 */ 311 public function countChanges($db, $res) { 312 return $this->adapter->countChanges($db, $res); 313 } 314 315} 316