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 16a1e6784eSAndreas Gohrclass helper_plugin_sqlite extends DokuWiki_Plugin { 17a1e6784eSAndreas Gohr var $db = null; 18a1e6784eSAndreas Gohr var $dbname = ''; 19a1e6784eSAndreas Gohr 20a1e6784eSAndreas Gohr function getInfo() { 21a1e6784eSAndreas Gohr return confToHash(dirname(__FILE__).'plugin.info.txt'); 22a1e6784eSAndreas Gohr } 23a1e6784eSAndreas Gohr 24a1e6784eSAndreas Gohr /** 25a1e6784eSAndreas Gohr * constructor 26a1e6784eSAndreas Gohr */ 27a1e6784eSAndreas Gohr function helper_plugin_sqlite(){ 28a1e6784eSAndreas Gohr if (!extension_loaded('sqlite')) { 29a1e6784eSAndreas Gohr $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : ''; 30a1e6784eSAndreas Gohr if(function_exists('dl')) @dl($prefix . 'sqlite.' . PHP_SHLIB_SUFFIX); 31a1e6784eSAndreas Gohr } 32a1e6784eSAndreas Gohr 33a1e6784eSAndreas Gohr if(!function_exists('sqlite_open')){ 34a1e6784eSAndreas Gohr msg('SQLite support missing in this PHP install - plugin will not work',-1); 35a1e6784eSAndreas Gohr } 36a1e6784eSAndreas Gohr } 37a1e6784eSAndreas Gohr 38a1e6784eSAndreas Gohr /** 39a1e6784eSAndreas Gohr * Initializes and opens the database 40a1e6784eSAndreas Gohr * 41a1e6784eSAndreas Gohr * Needs to be called right after loading this helper plugin 42a1e6784eSAndreas Gohr */ 43a1e6784eSAndreas Gohr function init($dbname,$updatedir){ 44a1e6784eSAndreas Gohr global $conf; 45a1e6784eSAndreas Gohr 46a1e6784eSAndreas Gohr // check for already open DB 47a1e6784eSAndreas Gohr if($this->db){ 48a1e6784eSAndreas Gohr if($this->dbname == $dbname){ 49a1e6784eSAndreas Gohr // db already open 50a1e6784eSAndreas Gohr return true; 51a1e6784eSAndreas Gohr } 52a1e6784eSAndreas Gohr // close other db 53a1e6784eSAndreas Gohr sqlite_close($this->db); 54a1e6784eSAndreas Gohr $this->db = null; 55a1e6784eSAndreas Gohr $this->dbname = ''; 56a1e6784eSAndreas Gohr } 57a1e6784eSAndreas Gohr 58a1e6784eSAndreas Gohr $this->dbname = $dbname; 59a1e6784eSAndreas Gohr $dbfile = $conf['metadir'].'/'.$dbname.'.sqlite'; 60a1e6784eSAndreas Gohr $init = (!@file_exists($dbfile) || ((int) @filesize($dbfile)) < 3); 61a1e6784eSAndreas Gohr 62a1e6784eSAndreas Gohr $error=''; 63a1e6784eSAndreas Gohr $this->db = sqlite_open($dbfile, 0666, $error); 64a1e6784eSAndreas Gohr if(!$this->db){ 65a1e6784eSAndreas Gohr msg("SQLite: failed to open SQLite ".$this->dbname." database ($error)",-1); 66a1e6784eSAndreas Gohr return false; 67a1e6784eSAndreas Gohr } 68a1e6784eSAndreas Gohr 69b5b947d7SAndreas Gohr // register our custom aggregate function 70b5b947d7SAndreas Gohr sqlite_create_aggregate($this->db,'group_concat', 71b5b947d7SAndreas Gohr array($this,'_sqlite_group_concat_step'), 72b5b947d7SAndreas Gohr array($this,'_sqlite_group_concat_finalize'), 2); 73b5b947d7SAndreas Gohr 74a1e6784eSAndreas Gohr $this->_updatedb($init,$updatedir); 75a1e6784eSAndreas Gohr return true; 76a1e6784eSAndreas Gohr } 77a1e6784eSAndreas Gohr 78a1e6784eSAndreas Gohr /** 79a1e6784eSAndreas Gohr * Return the current Database Version 80a1e6784eSAndreas Gohr */ 81a1e6784eSAndreas Gohr function _currentDBversion(){ 82a1e6784eSAndreas Gohr $sql = "SELECT val FROM opts WHERE opt = 'dbversion';"; 83a1e6784eSAndreas Gohr $res = $this->query($sql); 84a1e6784eSAndreas Gohr if(!$res) return false; 85a1e6784eSAndreas Gohr $row = $this->res2row($res,0); 86a1e6784eSAndreas Gohr return (int) $row['val']; 87a1e6784eSAndreas Gohr } 88a1e6784eSAndreas Gohr /** 89a1e6784eSAndreas Gohr * Update the database if needed 90a1e6784eSAndreas Gohr * 91a1e6784eSAndreas Gohr * @param bool $init - true if this is a new database to initialize 92a1e6784eSAndreas Gohr * @param string $updatedir - Database update infos 93a1e6784eSAndreas Gohr */ 94a1e6784eSAndreas Gohr function _updatedb($init,$updatedir){ 95a1e6784eSAndreas Gohr if($init){ 96a1e6784eSAndreas Gohr $current = 0; 97a1e6784eSAndreas Gohr }else{ 98a1e6784eSAndreas Gohr $current = $this->_currentDBversion(); 99a1e6784eSAndreas Gohr if(!$current){ 100a1e6784eSAndreas Gohr msg('SQLite: no DB version found. '.$this->dbname.' DB probably broken.',-1); 101a1e6784eSAndreas Gohr return false; 102a1e6784eSAndreas Gohr } 103a1e6784eSAndreas Gohr } 104a1e6784eSAndreas Gohr 105a1e6784eSAndreas Gohr // in case of init, add versioning table 106a1e6784eSAndreas Gohr if($init){ 107a1e6784eSAndreas Gohr if(!$this->_runupdatefile(dirname(__FILE__).'/db.sql',0)){ 108a1e6784eSAndreas Gohr msg('SQLite: '.$this->dbname.' database upgrade failed for version '.$i, -1); 109a1e6784eSAndreas Gohr return false; 110a1e6784eSAndreas Gohr } 111a1e6784eSAndreas Gohr } 112a1e6784eSAndreas Gohr 113a1e6784eSAndreas Gohr $latest = (int) trim(io_readFile($updatedir.'/latest.version')); 114a1e6784eSAndreas Gohr 115a1e6784eSAndreas Gohr // all up to date? 116a1e6784eSAndreas Gohr if($current >= $latest) return true; 117a1e6784eSAndreas Gohr for($i=$current+1; $i<=$latest; $i++){ 118a1e6784eSAndreas Gohr $file = sprintf($updatedir.'/update%04d.sql',$i); 119a1e6784eSAndreas Gohr if(file_exists($file)){ 120a1e6784eSAndreas Gohr if(!$this->_runupdatefile($file,$i)){ 121a1e6784eSAndreas Gohr msg('SQLite: '.$this->dbname.' database upgrade failed for version '.$i, -1); 122a1e6784eSAndreas Gohr 123a1e6784eSAndreas Gohr 124a1e6784eSAndreas Gohr return false; 125a1e6784eSAndreas Gohr } 126a1e6784eSAndreas Gohr } 127a1e6784eSAndreas Gohr } 128a1e6784eSAndreas Gohr return true; 129a1e6784eSAndreas Gohr } 130a1e6784eSAndreas Gohr 131a1e6784eSAndreas Gohr /** 132a1e6784eSAndreas Gohr * Updates the database structure using the given file to 133a1e6784eSAndreas Gohr * the given version. 134a1e6784eSAndreas Gohr */ 135a1e6784eSAndreas Gohr function _runupdatefile($file,$version){ 136a1e6784eSAndreas Gohr $sql = io_readFile($file,false); 137a1e6784eSAndreas Gohr 138a1e6784eSAndreas Gohr $sql = explode(";",$sql); 139a1e6784eSAndreas Gohr array_unshift($sql,'BEGIN TRANSACTION'); 140a1e6784eSAndreas Gohr array_push($sql,"INSERT OR REPLACE INTO opts (val,opt) VALUES ($version,'dbversion')"); 141a1e6784eSAndreas Gohr array_push($sql,"COMMIT TRANSACTION"); 142a1e6784eSAndreas Gohr 143a1e6784eSAndreas Gohr foreach($sql as $s){ 144a1e6784eSAndreas Gohr $s = preg_replace('!^\s*--.*$!m', '', $s); 145a1e6784eSAndreas Gohr $s = trim($s); 146a1e6784eSAndreas Gohr if(!$s) continue; 147a1e6784eSAndreas Gohr $res = $this->query("$s;"); 148a1e6784eSAndreas Gohr if ($res === false) { 149a1e6784eSAndreas Gohr sqlite_query($this->db, 'ROLLBACK TRANSACTION'); 150a1e6784eSAndreas Gohr return false; 151a1e6784eSAndreas Gohr } 152a1e6784eSAndreas Gohr } 153a1e6784eSAndreas Gohr 154a1e6784eSAndreas Gohr return ($version == $this->_currentDBversion()); 155a1e6784eSAndreas Gohr } 156a1e6784eSAndreas Gohr 157a1e6784eSAndreas Gohr /** 158a1e6784eSAndreas Gohr * Execute a query with the given parameters. 159a1e6784eSAndreas Gohr * 160a1e6784eSAndreas Gohr * Takes care of escaping 161a1e6784eSAndreas Gohr * 162a1e6784eSAndreas Gohr * @param string $sql - the statement 163a1e6784eSAndreas Gohr * @param arguments... 164a1e6784eSAndreas Gohr */ 165a1e6784eSAndreas Gohr function query(){ 166a1e6784eSAndreas Gohr if(!$this->db) return false; 167a1e6784eSAndreas Gohr 168a1e6784eSAndreas Gohr // get function arguments 169a1e6784eSAndreas Gohr $args = func_get_args(); 170a1e6784eSAndreas Gohr $sql = trim(array_shift($args)); 171a1e6784eSAndreas Gohr 172a1e6784eSAndreas Gohr if(!$sql){ 173a1e6784eSAndreas Gohr msg('No SQL statement given',-1); 174a1e6784eSAndreas Gohr return false; 175a1e6784eSAndreas Gohr } 176a1e6784eSAndreas Gohr 177a1e6784eSAndreas Gohr if(is_array($args[0])) $args = $args[0]; 178a1e6784eSAndreas Gohr $argc = count($args); 179a1e6784eSAndreas Gohr 180a1e6784eSAndreas Gohr // check number of arguments 181a1e6784eSAndreas Gohr if($argc < substr_count($sql,'?')){ 182a1e6784eSAndreas Gohr msg('Not enough arguments passed for statement. '. 183a1e6784eSAndreas Gohr 'Expected '.substr_count($sql,'?').' got '. 184a1e6784eSAndreas Gohr $argc.' - '.hsc($sql),-1); 185a1e6784eSAndreas Gohr return false; 186a1e6784eSAndreas Gohr } 187a1e6784eSAndreas Gohr 188a1e6784eSAndreas Gohr // explode at wildcard, then join again 189a1e6784eSAndreas Gohr $parts = explode('?',$sql,$argc+1); 190a1e6784eSAndreas Gohr $args = array_map(array($this,'quote_string'),$args); 191a1e6784eSAndreas Gohr $sql = ''; 192a1e6784eSAndreas Gohr 193a1e6784eSAndreas Gohr while( ($part = array_shift($parts)) !== null ){ 194a1e6784eSAndreas Gohr $sql .= $part; 195a1e6784eSAndreas Gohr $sql .= array_shift($args); 196a1e6784eSAndreas Gohr } 197a1e6784eSAndreas Gohr 198a1e6784eSAndreas Gohr // execute query 199a1e6784eSAndreas Gohr $err = ''; 200a1e6784eSAndreas Gohr $res = @sqlite_query($this->db,$sql,SQLITE_ASSOC,$err); 201a1e6784eSAndreas Gohr if($err){ 202*d9cff31cSAndreas Gohr msg($err.':<br /><pre>'.hsc($sql).'</pre>',-1); 203a1e6784eSAndreas Gohr return false; 204a1e6784eSAndreas Gohr }elseif(!$res){ 205a1e6784eSAndreas Gohr msg(sqlite_error_string(sqlite_last_error($this->db)). 206*d9cff31cSAndreas Gohr ':<br /><pre>'.hsc($sql).'</pre>',-1); 207a1e6784eSAndreas Gohr return false; 208a1e6784eSAndreas Gohr } 209a1e6784eSAndreas Gohr 210a1e6784eSAndreas Gohr return $res; 211a1e6784eSAndreas Gohr } 212a1e6784eSAndreas Gohr 213a1e6784eSAndreas Gohr /** 214a1e6784eSAndreas Gohr * Returns a complete result set as array 215a1e6784eSAndreas Gohr */ 216a1e6784eSAndreas Gohr function res2arr($res){ 217a1e6784eSAndreas Gohr $data = array(); 218a1e6784eSAndreas Gohr if(!sqlite_num_rows($res)) return $data; 219a1e6784eSAndreas Gohr sqlite_rewind($res); 220a1e6784eSAndreas Gohr while(($row = sqlite_fetch_array($res)) !== false){ 221a1e6784eSAndreas Gohr $data[] = $row; 222a1e6784eSAndreas Gohr } 223a1e6784eSAndreas Gohr return $data; 224a1e6784eSAndreas Gohr } 225a1e6784eSAndreas Gohr 226a1e6784eSAndreas Gohr /** 227a1e6784eSAndreas Gohr * Return the wanted row from a given result set as 228a1e6784eSAndreas Gohr * associative array 229a1e6784eSAndreas Gohr */ 230a1e6784eSAndreas Gohr function res2row($res,$rownum=0){ 231a1e6784eSAndreas Gohr if(!@sqlite_seek($res,$rownum)){ 232a1e6784eSAndreas Gohr return false; 233a1e6784eSAndreas Gohr } 234a1e6784eSAndreas Gohr return sqlite_fetch_array($res); 235a1e6784eSAndreas Gohr } 236a1e6784eSAndreas Gohr 237a1e6784eSAndreas Gohr 238a1e6784eSAndreas Gohr /** 239a1e6784eSAndreas Gohr * Join the given values and quote them for SQL insertion 240a1e6784eSAndreas Gohr */ 241a1e6784eSAndreas Gohr function quote_and_join($vals,$sep=',') { 242a1e6784eSAndreas Gohr $vals = array_map(array('helper_plugin_sqlite','quote_string'),$vals); 243a1e6784eSAndreas Gohr return join($sep,$vals); 244a1e6784eSAndreas Gohr } 245a1e6784eSAndreas Gohr 246a1e6784eSAndreas Gohr /** 247a1e6784eSAndreas Gohr * Run sqlite_escape_string() on the given string and surround it 248a1e6784eSAndreas Gohr * with quotes 249a1e6784eSAndreas Gohr */ 250a1e6784eSAndreas Gohr function quote_string($string){ 251a1e6784eSAndreas Gohr return "'".sqlite_escape_string($string)."'"; 252a1e6784eSAndreas Gohr } 253a1e6784eSAndreas Gohr 254a1e6784eSAndreas Gohr 255b5b947d7SAndreas Gohr /** 256b5b947d7SAndreas Gohr * Aggregation function for SQLite 257b5b947d7SAndreas Gohr * 258b5b947d7SAndreas Gohr * @link http://devzone.zend.com/article/863-SQLite-Lean-Mean-DB-Machine 259b5b947d7SAndreas Gohr */ 260b5b947d7SAndreas Gohr function _sqlite_group_concat_step(&$context, $string, $separator = ',') { 261b5b947d7SAndreas Gohr $context['sep'] = $separator; 262b5b947d7SAndreas Gohr $context['data'][] = $string; 263b5b947d7SAndreas Gohr } 264b5b947d7SAndreas Gohr 265b5b947d7SAndreas Gohr /** 266b5b947d7SAndreas Gohr * Aggregation function for SQLite 267b5b947d7SAndreas Gohr * 268b5b947d7SAndreas Gohr * @link http://devzone.zend.com/article/863-SQLite-Lean-Mean-DB-Machine 269b5b947d7SAndreas Gohr */ 270b5b947d7SAndreas Gohr function _sqlite_group_concat_finalize(&$context) { 271b5b947d7SAndreas Gohr $context['data'] = array_unique($context['data']); 272b5b947d7SAndreas Gohr return join($context['sep'],$context['data']); 273b5b947d7SAndreas Gohr } 274b5b947d7SAndreas Gohr 275b5b947d7SAndreas Gohr 276a1e6784eSAndreas Gohr} 277a1e6784eSAndreas Gohr 278a1e6784eSAndreas Gohr// vim:ts=4:sw=4:et:enc=utf-8: 279