1a1e6784eSAndreas Gohr<?php 2a1e6784eSAndreas Gohr/** 3a1e6784eSAndreas Gohr * DokuWiki Plugin sqlite (Helper Component) 4a1e6784eSAndreas Gohr * 5a1e6784eSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6a1e6784eSAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 7a1e6784eSAndreas Gohr */ 8a1e6784eSAndreas Gohr 9a1e6784eSAndreas Gohr// must be run within Dokuwiki 10a1e6784eSAndreas Gohrif (!defined('DOKU_INC')) die(); 11a1e6784eSAndreas Gohr 12a1e6784eSAndreas Gohrif (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 13a1e6784eSAndreas Gohrif (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 14a1e6784eSAndreas Gohrif (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 15a1e6784eSAndreas Gohr 1687fa2c18Sstretchyboyif (!defined('DOKU_EXT_SQLITE')) define('DOKU_EXT_SQLITE', 'sqlite'); 1787fa2c18Sstretchyboyif (!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo'); 1887fa2c18Sstretchyboy 19*aa81d781SKlap-inrequire_once(DOKU_PLUGIN.'sqlite/classes/adapter.php'); 2087fa2c18Sstretchyboy 21a1e6784eSAndreas Gohrclass helper_plugin_sqlite extends DokuWiki_Plugin { 22*aa81d781SKlap-in var $adapter = null; 23*aa81d781SKlap-in 24*aa81d781SKlap-in 25*aa81d781SKlap-in public function getInfo() { 26a1e6784eSAndreas Gohr return confToHash(dirname(__FILE__).'plugin.info.txt'); 27a1e6784eSAndreas Gohr } 28a1e6784eSAndreas Gohr 29a1e6784eSAndreas Gohr /** 30*aa81d781SKlap-in * Keep separate instances for every call to keep database connections 31*aa81d781SKlap-in */ 32*aa81d781SKlap-in public function isSingleton() { 33*aa81d781SKlap-in return false; 34*aa81d781SKlap-in } 35*aa81d781SKlap-in 36*aa81d781SKlap-in /** 37a1e6784eSAndreas Gohr * constructor 38a1e6784eSAndreas Gohr */ 39*aa81d781SKlap-in public function helper_plugin_sqlite(){ 4087fa2c18Sstretchyboy 41*aa81d781SKlap-in if(!$this->adapter) 4287fa2c18Sstretchyboy { 43*aa81d781SKlap-in if($this->existsPDOSqlite()){ 44*aa81d781SKlap-in require_once(DOKU_PLUGIN.'sqlite/classes/adapter_pdosqlite.php'); 45*aa81d781SKlap-in $this->adapter = new helper_plugin_sqlite_adapter_pdosqlite(); 46*aa81d781SKlap-in } 47*aa81d781SKlap-in } 48*aa81d781SKlap-in 49*aa81d781SKlap-in if(!$this->adapter) 50*aa81d781SKlap-in { 51*aa81d781SKlap-in if($this->existsSqlite2()){ 52*aa81d781SKlap-in require_once(DOKU_PLUGIN.'sqlite/classes/adapter_sqlite2.php'); 53*aa81d781SKlap-in $this->adapter = new helper_plugin_sqlite_adapter_sqlite2(); 54*aa81d781SKlap-in } 55*aa81d781SKlap-in } 56*aa81d781SKlap-in 57*aa81d781SKlap-in if(!$this->adapter) 58*aa81d781SKlap-in { 59*aa81d781SKlap-in msg('SQLite & PDO SQLite support missing in this PHP install - plugin will not work',-1); 60*aa81d781SKlap-in } 61*aa81d781SKlap-in } 62*aa81d781SKlap-in 63*aa81d781SKlap-in 64*aa81d781SKlap-in /** 65*aa81d781SKlap-in * check availabilty of PHPs sqlite extension (for sqlite2 support) 66*aa81d781SKlap-in */ 67*aa81d781SKlap-in public function existsSqlite2(){ 68*aa81d781SKlap-in if (!extension_loaded('sqlite')) { 69*aa81d781SKlap-in $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : ''; 70*aa81d781SKlap-in if(function_exists('dl')) @dl($prefix . 'sqlite.' . PHP_SHLIB_SUFFIX); 71*aa81d781SKlap-in } 72*aa81d781SKlap-in 73*aa81d781SKlap-in return function_exists('sqlite_open'); 74*aa81d781SKlap-in } 75*aa81d781SKlap-in 76*aa81d781SKlap-in /** 77*aa81d781SKlap-in * check availabilty of PHP PDO sqlite3 78*aa81d781SKlap-in */ 79*aa81d781SKlap-in public function existsPDOSqlite(){ 8087fa2c18Sstretchyboy if (!extension_loaded('pdo_sqlite')) { 8187fa2c18Sstretchyboy $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : ''; 8287fa2c18Sstretchyboy if(function_exists('dl')) @dl($prefix . 'pdo_sqlite.' . PHP_SHLIB_SUFFIX); 8387fa2c18Sstretchyboy } 8487fa2c18Sstretchyboy 8587fa2c18Sstretchyboy if(class_exists('pdo')){ 86*aa81d781SKlap-in foreach(PDO::getAvailableDrivers() as $driver){ 87*aa81d781SKlap-in if($driver=='sqlite'){ 88*aa81d781SKlap-in return true; 8987fa2c18Sstretchyboy } 9087fa2c18Sstretchyboy } 917ed6069fSAdrian Lang } 92*aa81d781SKlap-in return false; 93a1e6784eSAndreas Gohr } 94a1e6784eSAndreas Gohr 95a1e6784eSAndreas Gohr /** 96a1e6784eSAndreas Gohr * Initializes and opens the database 97a1e6784eSAndreas Gohr * 98a1e6784eSAndreas Gohr * Needs to be called right after loading this helper plugin 99*aa81d781SKlap-in * 100*aa81d781SKlap-in * @param string $dbname 101*aa81d781SKlap-in * @param string $updatedir - Database update infos 102*aa81d781SKlap-in * @return bool 103a1e6784eSAndreas Gohr */ 104*aa81d781SKlap-in public function init($dbname,$updatedir){ 105a1e6784eSAndreas Gohr 106*aa81d781SKlap-in $init = null; // set by initdb() 107*aa81d781SKlap-in if(!$this->adapter->initdb($dbname,$init)) return false; 108a1e6784eSAndreas Gohr 109*aa81d781SKlap-in return $this->_updatedb($init,$updatedir); 110a1e6784eSAndreas Gohr } 111a1e6784eSAndreas Gohr 112a1e6784eSAndreas Gohr /** 113a1e6784eSAndreas Gohr * Return the current Database Version 114a1e6784eSAndreas Gohr */ 115*aa81d781SKlap-in private function _currentDBversion(){ 116a1e6784eSAndreas Gohr $sql = "SELECT val FROM opts WHERE opt = 'dbversion';"; 117a1e6784eSAndreas Gohr $res = $this->query($sql); 118a1e6784eSAndreas Gohr if(!$res) return false; 119a1e6784eSAndreas Gohr $row = $this->res2row($res,0); 120a1e6784eSAndreas Gohr return (int) $row['val']; 121a1e6784eSAndreas Gohr } 122*aa81d781SKlap-in 123a1e6784eSAndreas Gohr /** 124a1e6784eSAndreas Gohr * Update the database if needed 125a1e6784eSAndreas Gohr * 126a1e6784eSAndreas Gohr * @param bool $init - true if this is a new database to initialize 127a1e6784eSAndreas Gohr * @param string $updatedir - Database update infos 128*aa81d781SKlap-in * @return bool 129a1e6784eSAndreas Gohr */ 130*aa81d781SKlap-in private function _updatedb($init,$updatedir) 1314d9093b4Sstretchyboy { 132a1e6784eSAndreas Gohr if($init){ 1334d9093b4Sstretchyboy 134a1e6784eSAndreas Gohr $current = 0; 135a1e6784eSAndreas Gohr }else{ 136a1e6784eSAndreas Gohr $current = $this->_currentDBversion(); 137a1e6784eSAndreas Gohr if(!$current){ 138*aa81d781SKlap-in msg("SQLite: no DB version found. '".$this->adapter->getDbname()."' DB probably broken.",-1); 139a1e6784eSAndreas Gohr return false; 140a1e6784eSAndreas Gohr } 141a1e6784eSAndreas Gohr } 142a1e6784eSAndreas Gohr 143a1e6784eSAndreas Gohr // in case of init, add versioning table 144a1e6784eSAndreas Gohr if($init){ 145a1e6784eSAndreas Gohr if(!$this->_runupdatefile(dirname(__FILE__).'/db.sql',0)){ 146*aa81d781SKlap-in msg("SQLite: '".$this->adapter->getDbname()."' database upgrade failed for version ", -1); 147a1e6784eSAndreas Gohr return false; 148a1e6784eSAndreas Gohr } 149a1e6784eSAndreas Gohr } 150a1e6784eSAndreas Gohr 151a1e6784eSAndreas Gohr $latest = (int) trim(io_readFile($updatedir.'/latest.version')); 152a1e6784eSAndreas Gohr 153a1e6784eSAndreas Gohr // all up to date? 154a1e6784eSAndreas Gohr if($current >= $latest) return true; 155a1e6784eSAndreas Gohr for($i=$current+1; $i<=$latest; $i++){ 156a1e6784eSAndreas Gohr $file = sprintf($updatedir.'/update%04d.sql',$i); 157a1e6784eSAndreas Gohr if(file_exists($file)){ 158a1e6784eSAndreas Gohr if(!$this->_runupdatefile($file,$i)){ 159*aa81d781SKlap-in msg("SQLite: '".$this->adapter->getDbname()."' database upgrade failed for version ".$i, -1); 160a1e6784eSAndreas Gohr return false; 161a1e6784eSAndreas Gohr } 162a1e6784eSAndreas Gohr } 163a1e6784eSAndreas Gohr } 164a1e6784eSAndreas Gohr return true; 165a1e6784eSAndreas Gohr } 166a1e6784eSAndreas Gohr 167a1e6784eSAndreas Gohr /** 168a1e6784eSAndreas Gohr * Updates the database structure using the given file to 169a1e6784eSAndreas Gohr * the given version. 170a1e6784eSAndreas Gohr */ 171*aa81d781SKlap-in private function _runupdatefile($file,$version){ 172a1e6784eSAndreas Gohr $sql = io_readFile($file,false); 173a1e6784eSAndreas Gohr 174a1e6784eSAndreas Gohr $sql = explode(";",$sql); 175a1e6784eSAndreas Gohr array_unshift($sql,'BEGIN TRANSACTION'); 176a1e6784eSAndreas Gohr array_push($sql,"INSERT OR REPLACE INTO opts (val,opt) VALUES ($version,'dbversion')"); 177a1e6784eSAndreas Gohr array_push($sql,"COMMIT TRANSACTION"); 178a1e6784eSAndreas Gohr 179a1e6784eSAndreas Gohr foreach($sql as $s){ 180a1e6784eSAndreas Gohr $s = preg_replace('!^\s*--.*$!m', '', $s); 181a1e6784eSAndreas Gohr $s = trim($s); 182a1e6784eSAndreas Gohr if(!$s) continue; 183fd69a32cSAndreas Gohr 184fd69a32cSAndreas Gohr 185a1e6784eSAndreas Gohr $res = $this->query("$s;"); 186a1e6784eSAndreas Gohr if ($res === false) { 187*aa81d781SKlap-in if($this->adapter->getName() == DOKU_EXT_SQLITE) 18887fa2c18Sstretchyboy { 189a1e6784eSAndreas Gohr sqlite_query($this->db, 'ROLLBACK TRANSACTION'); 190a1e6784eSAndreas Gohr } 19105f176edSstretchyboy return false; 19205f176edSstretchyboy } 193a1e6784eSAndreas Gohr } 194a1e6784eSAndreas Gohr 195a1e6784eSAndreas Gohr return ($version == $this->_currentDBversion()); 196a1e6784eSAndreas Gohr } 197a1e6784eSAndreas Gohr 198a1e6784eSAndreas Gohr /** 199*aa81d781SKlap-in * Dump db into a file in meta directory 200fd69a32cSAndreas Gohr * 201fd69a32cSAndreas Gohr */ 202*aa81d781SKlap-in public function dumpDatabase($dbname){ 203*aa81d781SKlap-in global $conf; 204fb394683SAndreas Gohr 205*aa81d781SKlap-in $res = $this->query("SELECT name,sql FROM sqlite_master WHERE type='table'"); 206*aa81d781SKlap-in $tables = $this->res2arr($res); 207fb394683SAndreas Gohr 208*aa81d781SKlap-in $filename = 'dumpfile_'.$dbname.'.sql'; 209*aa81d781SKlap-in if($fp = fopen($conf['metadir'].'/'.$filename, 'w')){ 210*aa81d781SKlap-in 211*aa81d781SKlap-in fwrite($fp, 'BEGIN TRANSACTION;'."\n"); 212*aa81d781SKlap-in 213*aa81d781SKlap-in foreach($tables as $table){ 214*aa81d781SKlap-in 215*aa81d781SKlap-in fwrite($fp, $table['sql'].";\n"); 216*aa81d781SKlap-in 217*aa81d781SKlap-in $sql = "SELECT * FROM ".$table['name']; 218*aa81d781SKlap-in $res = $this->query($sql); 219*aa81d781SKlap-in 220*aa81d781SKlap-in while($row = $this->res_fetch_array($res)){ 221*aa81d781SKlap-in 222*aa81d781SKlap-in $line = 'INSERT INTO '.$table['name'].' VALUES('; 223*aa81d781SKlap-in foreach($row as $no_entry => $entry){ 224*aa81d781SKlap-in if($no_entry!==0){ 225*aa81d781SKlap-in $line .= ','; 226fd69a32cSAndreas Gohr } 22787fa2c18Sstretchyboy 228*aa81d781SKlap-in if(is_null($entry)){ 229*aa81d781SKlap-in $line .= 'NULL'; 230*aa81d781SKlap-in }elseif(!is_numeric($entry)){ 231*aa81d781SKlap-in $line .= $this->quote_string($entry); 232fb394683SAndreas Gohr }else{ 233*aa81d781SKlap-in //FIXME extra leading zeros are truncated e.g 1.300 (thousand three hunderd)-> 1.3 234*aa81d781SKlap-in $line .= $entry; 235*aa81d781SKlap-in } 236*aa81d781SKlap-in } 237*aa81d781SKlap-in $line .= ');'."\n"; 238*aa81d781SKlap-in 239*aa81d781SKlap-in fwrite($fp, $line); 240*aa81d781SKlap-in } 241fb394683SAndreas Gohr } 242fb394683SAndreas Gohr 243*aa81d781SKlap-in $res = $this->query("SELECT name,sql FROM sqlite_master WHERE type='index'"); 244*aa81d781SKlap-in $indexes = $this->res2arr($res); 245*aa81d781SKlap-in foreach($indexes as $index){ 246*aa81d781SKlap-in fwrite($fp, $index['sql'].";\n"); 247fd69a32cSAndreas Gohr } 248fd69a32cSAndreas Gohr 249*aa81d781SKlap-in fwrite($fp, 'COMMIT;'."\n"); 250*aa81d781SKlap-in 251*aa81d781SKlap-in fclose($fp); 252*aa81d781SKlap-in return true; 253fd69a32cSAndreas Gohr }else{ 254*aa81d781SKlap-in msg('Dumping "'.hsc($dbname).'" has failed. Could not open '.$filename); 255fd69a32cSAndreas Gohr return false; 256fd69a32cSAndreas Gohr } 257fd69a32cSAndreas Gohr } 258fd69a32cSAndreas Gohr 259fd69a32cSAndreas Gohr /** 2603ae3f79eSKlap-in * Registers a User Defined Function for use in SQL statements 2613ae3f79eSKlap-in */ 262*aa81d781SKlap-in public function create_function($function_name,$callback,$num_args){ 263*aa81d781SKlap-in $this->adapter->create_function($function_name,$callback,$num_args); 2643ae3f79eSKlap-in } 2653ae3f79eSKlap-in 2663ae3f79eSKlap-in /** 267a1e6784eSAndreas Gohr * Execute a query with the given parameters. 268a1e6784eSAndreas Gohr * 269a1e6784eSAndreas Gohr * Takes care of escaping 270a1e6784eSAndreas Gohr * 271*aa81d781SKlap-in * @internal param string $sql - the statement 272*aa81d781SKlap-in * @internal param $arguments ... 273*aa81d781SKlap-in * @return bool|\SQLiteResult 274a1e6784eSAndreas Gohr */ 275*aa81d781SKlap-in public function query(){ 276a1e6784eSAndreas Gohr // get function arguments 277a1e6784eSAndreas Gohr $args = func_get_args(); 278a1e6784eSAndreas Gohr 279*aa81d781SKlap-in return $this->adapter->query($args); 28087fa2c18Sstretchyboy } 281a1e6784eSAndreas Gohr 282a1e6784eSAndreas Gohr /** 283a1e6784eSAndreas Gohr * Join the given values and quote them for SQL insertion 284a1e6784eSAndreas Gohr */ 285*aa81d781SKlap-in public function quote_and_join($vals,$sep=',') { 286*aa81d781SKlap-in return $this->adapter->quote_and_join($vals,$sep); 287a1e6784eSAndreas Gohr } 288a1e6784eSAndreas Gohr 289a1e6784eSAndreas Gohr /** 290a1e6784eSAndreas Gohr * Run sqlite_escape_string() on the given string and surround it 291a1e6784eSAndreas Gohr * with quotes 292a1e6784eSAndreas Gohr */ 293*aa81d781SKlap-in public function quote_string($string){ 294*aa81d781SKlap-in return $this->adapter->quote_string($string); 295a1e6784eSAndreas Gohr } 296a1e6784eSAndreas Gohr 297b5b947d7SAndreas Gohr /** 298fee3b689Sstretchyboy * Escape string for sql 299fee3b689Sstretchyboy */ 300*aa81d781SKlap-in public function escape_string($str){ 301*aa81d781SKlap-in return $this->adapter->escape_string($str); 302ff97cc8fSstretchyboy } 303ff97cc8fSstretchyboy 304ff97cc8fSstretchyboy /** 305*aa81d781SKlap-in * Returns a complete result set as array 306ff97cc8fSstretchyboy */ 307*aa81d781SKlap-in public function res2arr($res, $assoc = true){ 308*aa81d781SKlap-in return $this->adapter->res2arr($res, $assoc); 309b5b947d7SAndreas Gohr } 310b5b947d7SAndreas Gohr 311b5b947d7SAndreas Gohr /** 312*aa81d781SKlap-in * Return the wanted row from a given result set as 313*aa81d781SKlap-in * associative array 314b5b947d7SAndreas Gohr */ 315*aa81d781SKlap-in public function res2row($res,$rownum=0){ 316*aa81d781SKlap-in return $this->adapter->res2row($res,$rownum); 317b5b947d7SAndreas Gohr } 318b5b947d7SAndreas Gohr 319e7112ccbSAdrian Lang /** 320*aa81d781SKlap-in * Return the first value from the first row. 321e7112ccbSAdrian Lang */ 322*aa81d781SKlap-in public function res2single($res){ 323*aa81d781SKlap-in return $this->adapter->res2single($res); 324e7112ccbSAdrian Lang } 325fee3b689Sstretchyboy 326fee3b689Sstretchyboy /** 327fee3b689Sstretchyboy * fetch the next row as zero indexed array 328fee3b689Sstretchyboy */ 329*aa81d781SKlap-in public function res_fetch_array($res){ 330*aa81d781SKlap-in return $this->adapter->res_fetch_array($res); 33187fa2c18Sstretchyboy } 332fee3b689Sstretchyboy 333fee3b689Sstretchyboy 334fee3b689Sstretchyboy /** 335fee3b689Sstretchyboy * fetch the next row as assocative array 336fee3b689Sstretchyboy */ 337*aa81d781SKlap-in public function res_fetch_assoc($res){ 338*aa81d781SKlap-in return $this->adapter->res_fetch_assoc($res); 339fee3b689Sstretchyboy } 340fee3b689Sstretchyboy 341fee3b689Sstretchyboy /** 34278977d74SKlap-in * Count the number of records in result 3433157674bSAndreas Gohr * 344db58e525SKlap-in * This function is really inperformant in PDO and should be avoided! 345fee3b689Sstretchyboy */ 346*aa81d781SKlap-in public function res2count($res) { 347*aa81d781SKlap-in return $this->adapter->res2count($res); 348fee3b689Sstretchyboy } 34924a03f6cSstretchyboy 35024a03f6cSstretchyboy /** 35124a03f6cSstretchyboy * Count the number of records changed last time 35224a03f6cSstretchyboy */ 353*aa81d781SKlap-in public function countChanges($db, $res){ 354*aa81d781SKlap-in return $this->adapter->countChanges($db, $res); 355a1e6784eSAndreas Gohr } 356a1e6784eSAndreas Gohr 357*aa81d781SKlap-in} 358