193d995e0SAndreas Gohr<?php 293d995e0SAndreas Gohr/** 393d995e0SAndreas Gohr * DokuWiki Plugin sqlite (Admin Component) 493d995e0SAndreas Gohr * 593d995e0SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 693d995e0SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 793d995e0SAndreas Gohr */ 893d995e0SAndreas Gohr 9af0e7691SSzymon Olewniczakuse dokuwiki\Form\Form; 10ecc45fdcSSzymon Olewniczakuse dokuwiki\Form\InputElement; 11*3a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\QuerySaver; 12*3a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 13*3a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\Tools; 14af0e7691SSzymon Olewniczak 1593d995e0SAndreas Gohr 16*3a56750bSAndreas Gohrclass admin_plugin_sqlite extends DokuWiki_Admin_Plugin 17*3a56750bSAndreas Gohr{ 18*3a56750bSAndreas Gohr /** @var SQLiteDB */ 19*3a56750bSAndreas Gohr protected $db = null; 2093d995e0SAndreas Gohr 21*3a56750bSAndreas Gohr /** @var QuerySaver */ 22*3a56750bSAndreas Gohr protected $querySaver = null; 23*3a56750bSAndreas Gohr 24*3a56750bSAndreas Gohr /** @inheritdoc */ 25*3a56750bSAndreas Gohr function getMenuSort() 26*3a56750bSAndreas Gohr { 270542fc8eSKlap-in return 500; 280542fc8eSKlap-in } 290542fc8eSKlap-in 30*3a56750bSAndreas Gohr /** @inheritdoc */ 31*3a56750bSAndreas Gohr function forAdminOnly() 32*3a56750bSAndreas Gohr { 330542fc8eSKlap-in return true; 340542fc8eSKlap-in } 3593d995e0SAndreas Gohr 36*3a56750bSAndreas Gohr /** @inheritdoc */ 37*3a56750bSAndreas Gohr function handle() 38*3a56750bSAndreas Gohr { 39a34ef333SKlap-in global $conf; 400dbd1d4cSSzymon Olewniczak global $INPUT; 410dbd1d4cSSzymon Olewniczak 42*3a56750bSAndreas Gohr // load database if given and security token is valid 43*3a56750bSAndreas Gohr if ($INPUT->str('db') && checkSecurityToken()) { 44*3a56750bSAndreas Gohr try { 45*3a56750bSAndreas Gohr $this->db = new SQLiteDB($INPUT->str('db'), ''); 46*3a56750bSAndreas Gohr $this->querySaver = new QuerySaver($this->db->getDBName()); 47*3a56750bSAndreas Gohr } catch (Exception $e) { 48*3a56750bSAndreas Gohr msg($e->getMessage(), -1); 49*3a56750bSAndreas Gohr return; 50a34ef333SKlap-in } 51*3a56750bSAndreas Gohr } 520dbd1d4cSSzymon Olewniczak 53*3a56750bSAndreas Gohr $cmd = $INPUT->extract('cmd')->str('cmd'); 54*3a56750bSAndreas Gohr switch ($cmd) { 55*3a56750bSAndreas Gohr case 'export': 56*3a56750bSAndreas Gohr $exportfile = $conf['tmpdir'] . '/' . $this->db->getDBName() . '.sql'; 57*3a56750bSAndreas Gohr $this->db->dumpToFile($exportfile); 580dbd1d4cSSzymon Olewniczak header('Content-Type: text/sql'); 59*3a56750bSAndreas Gohr header('Content-Disposition: attachment; filename="' . $this->db->getDbName() . '.sql";'); 60*3a56750bSAndreas Gohr readfile($exportfile); 61*3a56750bSAndreas Gohr unlink($exportfile); 620dbd1d4cSSzymon Olewniczak exit(0); 63*3a56750bSAndreas Gohr case 'import': 64*3a56750bSAndreas Gohr $importfile = $_FILES['importfile']['tmp_name']; 650dbd1d4cSSzymon Olewniczak 66*3a56750bSAndreas Gohr if (empty($importfile)) { 670dbd1d4cSSzymon Olewniczak msg($this->getLang('import_no_file'), -1); 680dbd1d4cSSzymon Olewniczak return; 690dbd1d4cSSzymon Olewniczak } 700dbd1d4cSSzymon Olewniczak 71*3a56750bSAndreas Gohr $sql = file_get_contents($importfile); 72*3a56750bSAndreas Gohr try { 73*3a56750bSAndreas Gohr $this->db->exec($sql); 740dbd1d4cSSzymon Olewniczak msg($this->getLang('import_success'), 1); 75*3a56750bSAndreas Gohr } catch (Exception $e) { 76*3a56750bSAndreas Gohr msg($e->getMessage(), -1); 770dbd1d4cSSzymon Olewniczak } 78*3a56750bSAndreas Gohr break; 79*3a56750bSAndreas Gohr case 'save_query': 80*3a56750bSAndreas Gohr $this->querySaver->saveQuery($INPUT->str('name'), $INPUT->str('sql')); 81*3a56750bSAndreas Gohr break; 82*3a56750bSAndreas Gohr case 'delete_query': 83*3a56750bSAndreas Gohr $this->querySaver->deleteQuery($INPUT->str('name')); 84*3a56750bSAndreas Gohr break; 85a34ef333SKlap-in } 8693d995e0SAndreas Gohr } 8793d995e0SAndreas Gohr 88*3a56750bSAndreas Gohr /** @inheritdoc */ 89*3a56750bSAndreas Gohr function html() 90*3a56750bSAndreas Gohr { 91271f9caeSSzymon Olewniczak global $INPUT; 9293d995e0SAndreas Gohr 9393d995e0SAndreas Gohr echo $this->locale_xhtml('intro'); 94*3a56750bSAndreas Gohr if (!$this->db) return; 9593d995e0SAndreas Gohr 9693d995e0SAndreas Gohr 97*3a56750bSAndreas Gohr echo '<h2>' . $this->getLang('db') . ' "' . hsc($this->db->getDbName()) . '"</h2>'; 9893d995e0SAndreas Gohr echo '<div class="level2">'; 9993d995e0SAndreas Gohr 100*3a56750bSAndreas Gohr echo '<div class="commands">'; 101*3a56750bSAndreas Gohr $this->showCommands(); 102*3a56750bSAndreas Gohr $this->showSavedQueries(); 103*3a56750bSAndreas Gohr echo '</div>'; 104a34ef333SKlap-in 105*3a56750bSAndreas Gohr // query form 106*3a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink()]); 107*3a56750bSAndreas Gohr $form->addClass('sqliteplugin'); 108ecc45fdcSSzymon Olewniczak $form->addFieldsetOpen('SQL Command'); 109*3a56750bSAndreas Gohr $form->addTextarea('sql')->addClass('edit'); 110*3a56750bSAndreas Gohr $form->addButton('submit', $this->getLang('btn_execute'))->attr('type', 'submit'); 111ecc45fdcSSzymon Olewniczak $form->addTextInput('name', $this->getLang('query_name')); 112*3a56750bSAndreas Gohr $form->addButton('cmd[save_query]', $this->getLang('save_query'))->attr('type', 'submit'); 113ecc45fdcSSzymon Olewniczak $form->addFieldsetClose(); 114*3a56750bSAndreas Gohr echo $form->toHTML(); 11593d995e0SAndreas Gohr 116*3a56750bSAndreas Gohr // results 117*3a56750bSAndreas Gohr if ($INPUT->has('sql')) $this->showQueryResults($INPUT->str('sql')); 118271f9caeSSzymon Olewniczak echo '</div>'; 119271f9caeSSzymon Olewniczak } 120271f9caeSSzymon Olewniczak 121*3a56750bSAndreas Gohr function getTOC() 122*3a56750bSAndreas Gohr { 12393d995e0SAndreas Gohr global $conf; 12493d995e0SAndreas Gohr global $ID; 12593d995e0SAndreas Gohr 12693d995e0SAndreas Gohr $toc = array(); 12740698f67SKlap-in $fileextensions = array('sqlite2' => '.sqlite', 'sqlite3' => '.sqlite3'); 12893d995e0SAndreas Gohr 12940698f67SKlap-in foreach ($fileextensions as $dbformat => $fileextension) { 13093d995e0SAndreas Gohr $toc[] = array( 1310542fc8eSKlap-in 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite')), 13240698f67SKlap-in 'title' => $dbformat . ':', 13393d995e0SAndreas Gohr 'level' => 1, 13493d995e0SAndreas Gohr 'type' => 'ul', 13593d995e0SAndreas Gohr ); 13640698f67SKlap-in 13740698f67SKlap-in $dbfiles = glob($conf['metadir'] . '/*' . $fileextension); 13840698f67SKlap-in 13940698f67SKlap-in if (is_array($dbfiles)) foreach ($dbfiles as $file) { 14040698f67SKlap-in $db = basename($file, $fileextension); 14140698f67SKlap-in $toc[] = array( 14284041991SKlap-in 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite', 'db' => $db, 'version' => $dbformat, 'sectok' => getSecurityToken())), 14340698f67SKlap-in 'title' => $this->getLang('db') . ' ' . $db, 14440698f67SKlap-in 'level' => 2, 14540698f67SKlap-in 'type' => 'ul', 14640698f67SKlap-in ); 14740698f67SKlap-in } 14893d995e0SAndreas Gohr } 14993d995e0SAndreas Gohr 15093d995e0SAndreas Gohr return $toc; 15193d995e0SAndreas Gohr } 152*3a56750bSAndreas Gohr 153*3a56750bSAndreas Gohr /** 154*3a56750bSAndreas Gohr * Execute and display the results of the given SQL query 155*3a56750bSAndreas Gohr * 156*3a56750bSAndreas Gohr * multiple queries can be given separated by semicolons 157*3a56750bSAndreas Gohr * 158*3a56750bSAndreas Gohr * @param string $sql 159*3a56750bSAndreas Gohr */ 160*3a56750bSAndreas Gohr protected function showQueryResults($sql) 161*3a56750bSAndreas Gohr { 162*3a56750bSAndreas Gohr echo '<h3 id="scroll__here">Query results</h3>'; 163*3a56750bSAndreas Gohr 164*3a56750bSAndreas Gohr $sql = Tools::SQLstring2array($sql); 165*3a56750bSAndreas Gohr foreach ($sql as $s) { 166*3a56750bSAndreas Gohr $s = preg_replace('!^\s*--.*$!m', '', $s); 167*3a56750bSAndreas Gohr $s = trim($s); 168*3a56750bSAndreas Gohr if (!$s) continue; 169*3a56750bSAndreas Gohr 170*3a56750bSAndreas Gohr try { 171*3a56750bSAndreas Gohr $time_start = microtime(true); 172*3a56750bSAndreas Gohr $result = $this->db->queryAll($s); 173*3a56750bSAndreas Gohr $time_end = microtime(true); 174*3a56750bSAndreas Gohr } catch (Exception $e) { 175*3a56750bSAndreas Gohr msg($e->getMessage(), -1); 176*3a56750bSAndreas Gohr continue; 17793d995e0SAndreas Gohr } 17893d995e0SAndreas Gohr 179*3a56750bSAndreas Gohr $time = $time_end - $time_start; 180*3a56750bSAndreas Gohr $cnt = count($result); 181*3a56750bSAndreas Gohr msg($cnt . ' affected rows in ' . $this->microtimeToSeconds($time) . ' seconds', 1); 182*3a56750bSAndreas Gohr if (!$cnt) continue; 183*3a56750bSAndreas Gohr 184*3a56750bSAndreas Gohr echo '<div>'; 185*3a56750bSAndreas Gohr $ths = array_keys($result[0]); 186*3a56750bSAndreas Gohr echo '<table class="inline">'; 187*3a56750bSAndreas Gohr echo '<tr>'; 188*3a56750bSAndreas Gohr foreach ($ths as $th) { 189*3a56750bSAndreas Gohr echo '<th>' . hsc($th) . '</th>'; 190*3a56750bSAndreas Gohr } 191*3a56750bSAndreas Gohr echo '</tr>'; 192*3a56750bSAndreas Gohr foreach ($result as $row) { 193*3a56750bSAndreas Gohr echo '<tr>'; 194*3a56750bSAndreas Gohr $tds = array_values($row); 195*3a56750bSAndreas Gohr foreach ($tds as $td) { 196*3a56750bSAndreas Gohr if ($td === null) $td = '␀'; 197*3a56750bSAndreas Gohr echo '<td>' . hsc($td) . '</td>'; 198*3a56750bSAndreas Gohr } 199*3a56750bSAndreas Gohr echo '</tr>'; 200*3a56750bSAndreas Gohr } 201*3a56750bSAndreas Gohr echo '</table>'; 202*3a56750bSAndreas Gohr echo '</div>'; 203*3a56750bSAndreas Gohr } 204*3a56750bSAndreas Gohr } 205*3a56750bSAndreas Gohr 206*3a56750bSAndreas Gohr 207*3a56750bSAndreas Gohr /** 208*3a56750bSAndreas Gohr * Convert a microtime() value to a string in seconds 209*3a56750bSAndreas Gohr * 210*3a56750bSAndreas Gohr * @param float $time 211*3a56750bSAndreas Gohr * @return string 212*3a56750bSAndreas Gohr */ 213*3a56750bSAndreas Gohr protected function microtimeToSeconds($time) 214*3a56750bSAndreas Gohr { 215*3a56750bSAndreas Gohr return ($time < 0.0001 ? substr($time, 0, 5) . substr($time, -3) : substr($time, 0, 7)); 216*3a56750bSAndreas Gohr } 217*3a56750bSAndreas Gohr 218*3a56750bSAndreas Gohr /** 219*3a56750bSAndreas Gohr * Construct a link to the sqlite admin page with the given additional parameters 220*3a56750bSAndreas Gohr * 221*3a56750bSAndreas Gohr * Basically a wrapper around wl() with some defaults 222*3a56750bSAndreas Gohr * 223*3a56750bSAndreas Gohr * @param string[] $params 224*3a56750bSAndreas Gohr * @param bool $form for use in form action? 225*3a56750bSAndreas Gohr * @return string 226*3a56750bSAndreas Gohr */ 227*3a56750bSAndreas Gohr protected function selfLink($form = true, $params = []) 228*3a56750bSAndreas Gohr { 229*3a56750bSAndreas Gohr global $ID; 230*3a56750bSAndreas Gohr $params = array_merge( 231*3a56750bSAndreas Gohr [ 232*3a56750bSAndreas Gohr 'do' => 'admin', 233*3a56750bSAndreas Gohr 'page' => 'sqlite', 234*3a56750bSAndreas Gohr 'db' => $this->db ? $this->db->getDBName() : '', 235*3a56750bSAndreas Gohr 'sectok' => getSecurityToken(), 236*3a56750bSAndreas Gohr ], $params 237*3a56750bSAndreas Gohr ); 238*3a56750bSAndreas Gohr 239*3a56750bSAndreas Gohr return wl($ID, $params, false, $form ? '&' : '&'); 240*3a56750bSAndreas Gohr } 241*3a56750bSAndreas Gohr 242*3a56750bSAndreas Gohr /** 243*3a56750bSAndreas Gohr * Display the standard actions for a database 244*3a56750bSAndreas Gohr */ 245*3a56750bSAndreas Gohr protected function showCommands() 246*3a56750bSAndreas Gohr { 247*3a56750bSAndreas Gohr $commands = [ 248*3a56750bSAndreas Gohr 'dbversion' => [ 249*3a56750bSAndreas Gohr 'sql' => 'SELECT val FROM opts WHERE opt=\'dbversion\'', 250*3a56750bSAndreas Gohr ], 251*3a56750bSAndreas Gohr 'table' => [ 252*3a56750bSAndreas Gohr 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'table\' ORDER BY name', 253*3a56750bSAndreas Gohr ], 254*3a56750bSAndreas Gohr 'index' => [ 255*3a56750bSAndreas Gohr 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'index\' ORDER BY name', 256*3a56750bSAndreas Gohr ], 257*3a56750bSAndreas Gohr 'export' => [ 258*3a56750bSAndreas Gohr 'cmd' => 'export' 259*3a56750bSAndreas Gohr ], 260*3a56750bSAndreas Gohr ]; 261*3a56750bSAndreas Gohr 262*3a56750bSAndreas Gohr // import form 263*3a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink(), 'enctype' => 'multipart/form-data', 'method' => 'post']); 264*3a56750bSAndreas Gohr $form->addElement(new InputElement('file', 'importfile')); 265*3a56750bSAndreas Gohr $form->addButton('cmd[import]', $this->getLang('import')); 266*3a56750bSAndreas Gohr 267*3a56750bSAndreas Gohr // output as a list 268*3a56750bSAndreas Gohr echo '<ul>'; 269*3a56750bSAndreas Gohr foreach ($commands as $label => $command) { 270*3a56750bSAndreas Gohr echo '<li><div class="li">'; 271*3a56750bSAndreas Gohr echo '<a href="' . $this->selfLink(false, $command) . '">' . $this->getLang($label) . '</a>'; 272*3a56750bSAndreas Gohr echo '</div></li>'; 273*3a56750bSAndreas Gohr } 274*3a56750bSAndreas Gohr echo '<li><div class="li">'; 275*3a56750bSAndreas Gohr echo $form->toHTML(); 276*3a56750bSAndreas Gohr echo '</div></li>'; 277*3a56750bSAndreas Gohr echo '</ul>'; 278*3a56750bSAndreas Gohr } 279*3a56750bSAndreas Gohr 280*3a56750bSAndreas Gohr /** 281*3a56750bSAndreas Gohr * FIXME needs to be cleaned up 282*3a56750bSAndreas Gohr */ 283*3a56750bSAndreas Gohr public function showSavedQueries() 284*3a56750bSAndreas Gohr { 285*3a56750bSAndreas Gohr $queries = $this->querySaver->getQueries(); 286*3a56750bSAndreas Gohr if (!$queries) return; 287*3a56750bSAndreas Gohr 288*3a56750bSAndreas Gohr echo '<ul>'; 289*3a56750bSAndreas Gohr foreach ($queries as $query) { 290*3a56750bSAndreas Gohr $link = $this->selfLink(false, ['sql' => $query['sql']]); 291*3a56750bSAndreas Gohr $del = $this->selfLink(false, ['cmd' => 'delete_query', 'name' => $query['name']]); 292*3a56750bSAndreas Gohr 293*3a56750bSAndreas Gohr echo '<li><div class="li">'; 294*3a56750bSAndreas Gohr echo '<a href="' . $link . '">' . hsc($query['name']) . '</a>'; 295*3a56750bSAndreas Gohr echo ' [<a href="' . $del . '">' . $this->getLang('delete_query') . '</a>]'; 296*3a56750bSAndreas Gohr echo '</div></li>'; 297*3a56750bSAndreas Gohr } 298*3a56750bSAndreas Gohr echo '</ul>'; 299*3a56750bSAndreas Gohr } 300*3a56750bSAndreas Gohr} 301