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 = 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 * Registers a User Defined Function for use in SQL statements 192 */ 193 public function create_function($function_name, $callback, $num_args) { 194 $this->adapter->create_function($function_name, $callback, $num_args); 195 } 196 197 /** 198 * Execute a query with the given parameters. 199 * 200 * Takes care of escaping 201 * 202 * @internal param string $sql - the statement 203 * @internal param $arguments ... 204 * @return bool|\SQLiteResult 205 */ 206 public function query() { 207 // get function arguments 208 $args = func_get_args(); 209 210 return $this->adapter->query($args); 211 } 212 213 /** 214 * Join the given values and quote them for SQL insertion 215 */ 216 public function quote_and_join($vals, $sep = ',') { 217 return $this->adapter->quote_and_join($vals, $sep); 218 } 219 220 /** 221 * Run sqlite_escape_string() on the given string and surround it 222 * with quotes 223 */ 224 public function quote_string($string) { 225 return $this->adapter->quote_string($string); 226 } 227 228 /** 229 * Escape string for sql 230 */ 231 public function escape_string($str) { 232 return $this->adapter->escape_string($str); 233 } 234 235 /** 236 * Returns a complete result set as array 237 */ 238 public function res2arr($res, $assoc = true) { 239 return $this->adapter->res2arr($res, $assoc); 240 } 241 242 /** 243 * Return the wanted row from a given result set as 244 * associative array 245 */ 246 public function res2row($res, $rownum = 0) { 247 return $this->adapter->res2row($res, $rownum); 248 } 249 250 /** 251 * Return the first value from the first row. 252 */ 253 public function res2single($res) { 254 return $this->adapter->res2single($res); 255 } 256 257 /** 258 * fetch the next row as zero indexed array 259 */ 260 public function res_fetch_array($res) { 261 return $this->adapter->res_fetch_array($res); 262 } 263 264 /** 265 * fetch the next row as assocative array 266 */ 267 public function res_fetch_assoc($res) { 268 return $this->adapter->res_fetch_assoc($res); 269 } 270 271 /** 272 * Count the number of records in result 273 * 274 * This function is really inperformant in PDO and should be avoided! 275 */ 276 public function res2count($res) { 277 return $this->adapter->res2count($res); 278 } 279 280 /** 281 * Count the number of records changed last time 282 */ 283 public function countChanges($db, $res) { 284 return $this->adapter->countChanges($db, $res); 285 } 286 287} 288