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->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 = explode(";", $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 foreach($sql as $s) { 174 $s = preg_replace('!^\s*--.*$!m', '', $s); 175 $s = trim($s); 176 if(!$s) continue; 177 178 $res = $this->query("$s;"); 179 if($res === false) { 180 if($this->adapter->getName() == DOKU_EXT_SQLITE) { 181 sqlite_query($this->db, 'ROLLBACK TRANSACTION'); 182 } 183 return false; 184 } 185 } 186 187 return ($version == $this->_currentDBversion()); 188 } 189 190 /** 191 * Dump db into a file in meta directory 192 * 193 */ 194 public function dumpDatabase($dbname) { 195 global $conf; 196 197 $res = $this->query("SELECT name,sql FROM sqlite_master WHERE type='table'"); 198 $tables = $this->res2arr($res); 199 200 $filename = 'dumpfile_'.$dbname.'.sql'; 201 if($fp = fopen($conf['metadir'].'/'.$filename, 'w')) { 202 203 fwrite($fp, 'BEGIN TRANSACTION;'."\n"); 204 205 foreach($tables as $table) { 206 207 fwrite($fp, $table['sql'].";\n"); 208 209 $sql = "SELECT * FROM ".$table['name']; 210 $res = $this->query($sql); 211 212 while($row = $this->res_fetch_array($res)) { 213 214 $line = 'INSERT INTO '.$table['name'].' VALUES('; 215 foreach($row as $no_entry => $entry) { 216 if($no_entry !== 0) { 217 $line .= ','; 218 } 219 220 if(is_null($entry)) { 221 $line .= 'NULL'; 222 } elseif(!is_numeric($entry)) { 223 $line .= $this->quote_string($entry); 224 } else { 225 //FIXME extra leading zeros are truncated e.g 1.300 (thousand three hunderd)-> 1.3 226 $line .= $entry; 227 } 228 } 229 $line .= ');'."\n"; 230 231 fwrite($fp, $line); 232 } 233 } 234 235 $res = $this->query("SELECT name,sql FROM sqlite_master WHERE type='index'"); 236 $indexes = $this->res2arr($res); 237 foreach($indexes as $index) { 238 fwrite($fp, $index['sql'].";\n"); 239 } 240 241 fwrite($fp, 'COMMIT;'."\n"); 242 243 fclose($fp); 244 return true; 245 } else { 246 msg('Dumping "'.hsc($dbname).'" has failed. Could not open '.$filename); 247 return false; 248 } 249 } 250 251 /** 252 * Registers a User Defined Function for use in SQL statements 253 */ 254 public function create_function($function_name, $callback, $num_args) { 255 $this->adapter->create_function($function_name, $callback, $num_args); 256 } 257 258 /** 259 * Execute a query with the given parameters. 260 * 261 * Takes care of escaping 262 * 263 * @internal param string $sql - the statement 264 * @internal param $arguments ... 265 * @return bool|\SQLiteResult 266 */ 267 public function query() { 268 // get function arguments 269 $args = func_get_args(); 270 271 return $this->adapter->query($args); 272 } 273 274 /** 275 * Join the given values and quote them for SQL insertion 276 */ 277 public function quote_and_join($vals, $sep = ',') { 278 return $this->adapter->quote_and_join($vals, $sep); 279 } 280 281 /** 282 * Run sqlite_escape_string() on the given string and surround it 283 * with quotes 284 */ 285 public function quote_string($string) { 286 return $this->adapter->quote_string($string); 287 } 288 289 /** 290 * Escape string for sql 291 */ 292 public function escape_string($str) { 293 return $this->adapter->escape_string($str); 294 } 295 296 /** 297 * Returns a complete result set as array 298 */ 299 public function res2arr($res) { 300 return $this->adapter->res2arr($res); 301 } 302 303 /** 304 * Return the wanted row from a given result set as 305 * associative array 306 */ 307 public function res2row($res, $rownum = 0) { 308 return $this->adapter->res2row($res, $rownum); 309 } 310 311 /** 312 * Return the first value from the first row. 313 */ 314 public function res2single($res) { 315 return $this->adapter->res2single($res); 316 } 317 318 /** 319 * fetch the next row as zero indexed array 320 */ 321 public function res_fetch_array($res) { 322 return $this->adapter->res_fetch_array($res); 323 } 324 325 /** 326 * fetch the next row as assocative array 327 */ 328 public function res_fetch_assoc($res) { 329 return $this->adapter->res_fetch_assoc($res); 330 } 331 332 /** 333 * Count the number of records in result 334 * 335 * This function is really inperformant in PDO and should be avoided! 336 */ 337 public function res2count($res) { 338 return $this->adapter->res2count($res); 339 } 340 341 /** 342 * Count the number of records changed last time 343 */ 344 public function countChanges($db, $res) { 345 return $this->adapter->countChanges($db, $res); 346 } 347 348} 349