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; 113a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\QuerySaver; 123a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 133a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\Tools; 14af0e7691SSzymon Olewniczak 1593d995e0SAndreas Gohr 163a56750bSAndreas Gohrclass admin_plugin_sqlite extends DokuWiki_Admin_Plugin 173a56750bSAndreas Gohr{ 183a56750bSAndreas Gohr /** @var SQLiteDB */ 193a56750bSAndreas Gohr protected $db = null; 2093d995e0SAndreas Gohr 213a56750bSAndreas Gohr /** @var QuerySaver */ 223a56750bSAndreas Gohr protected $querySaver = null; 233a56750bSAndreas Gohr 243a56750bSAndreas Gohr /** @inheritdoc */ 253a56750bSAndreas Gohr function getMenuSort() 263a56750bSAndreas Gohr { 270542fc8eSKlap-in return 500; 280542fc8eSKlap-in } 290542fc8eSKlap-in 303a56750bSAndreas Gohr /** @inheritdoc */ 313a56750bSAndreas Gohr function forAdminOnly() 323a56750bSAndreas Gohr { 330542fc8eSKlap-in return true; 340542fc8eSKlap-in } 3593d995e0SAndreas Gohr 363a56750bSAndreas Gohr /** @inheritdoc */ 373a56750bSAndreas Gohr function handle() 383a56750bSAndreas Gohr { 39a34ef333SKlap-in global $conf; 400dbd1d4cSSzymon Olewniczak global $INPUT; 410dbd1d4cSSzymon Olewniczak 423a56750bSAndreas Gohr // load database if given and security token is valid 433a56750bSAndreas Gohr if ($INPUT->str('db') && checkSecurityToken()) { 443a56750bSAndreas Gohr try { 453a56750bSAndreas Gohr $this->db = new SQLiteDB($INPUT->str('db'), ''); 463a56750bSAndreas Gohr $this->querySaver = new QuerySaver($this->db->getDBName()); 473a56750bSAndreas Gohr } catch (Exception $e) { 483a56750bSAndreas Gohr msg($e->getMessage(), -1); 493a56750bSAndreas Gohr return; 50a34ef333SKlap-in } 513a56750bSAndreas Gohr } 520dbd1d4cSSzymon Olewniczak 533a56750bSAndreas Gohr $cmd = $INPUT->extract('cmd')->str('cmd'); 543a56750bSAndreas Gohr switch ($cmd) { 553a56750bSAndreas Gohr case 'export': 563a56750bSAndreas Gohr $exportfile = $conf['tmpdir'] . '/' . $this->db->getDBName() . '.sql'; 573a56750bSAndreas Gohr $this->db->dumpToFile($exportfile); 580dbd1d4cSSzymon Olewniczak header('Content-Type: text/sql'); 593a56750bSAndreas Gohr header('Content-Disposition: attachment; filename="' . $this->db->getDbName() . '.sql";'); 603a56750bSAndreas Gohr readfile($exportfile); 613a56750bSAndreas Gohr unlink($exportfile); 620dbd1d4cSSzymon Olewniczak exit(0); 633a56750bSAndreas Gohr case 'import': 643a56750bSAndreas Gohr $importfile = $_FILES['importfile']['tmp_name']; 650dbd1d4cSSzymon Olewniczak 663a56750bSAndreas Gohr if (empty($importfile)) { 670dbd1d4cSSzymon Olewniczak msg($this->getLang('import_no_file'), -1); 680dbd1d4cSSzymon Olewniczak return; 690dbd1d4cSSzymon Olewniczak } 700dbd1d4cSSzymon Olewniczak 71*7ddaad11SAndreas Gohr $sql = Tools::SQLstring2array(file_get_contents($importfile)); 723a56750bSAndreas Gohr try { 73*7ddaad11SAndreas Gohr $this->db->getDb()->beginTransaction(); 74*7ddaad11SAndreas Gohr foreach ($sql as $s) { 75*7ddaad11SAndreas Gohr $this->db->exec($s); 76*7ddaad11SAndreas Gohr } 77*7ddaad11SAndreas Gohr $this->db->getDb()->commit(); 780dbd1d4cSSzymon Olewniczak msg($this->getLang('import_success'), 1); 793a56750bSAndreas Gohr } catch (Exception $e) { 80*7ddaad11SAndreas Gohr $this->db->getDb()->rollBack(); 81*7ddaad11SAndreas Gohr msg(hsc($e->getMessage()), -1); 820dbd1d4cSSzymon Olewniczak } 833a56750bSAndreas Gohr break; 843a56750bSAndreas Gohr case 'save_query': 853a56750bSAndreas Gohr $this->querySaver->saveQuery($INPUT->str('name'), $INPUT->str('sql')); 863a56750bSAndreas Gohr break; 873a56750bSAndreas Gohr case 'delete_query': 883a56750bSAndreas Gohr $this->querySaver->deleteQuery($INPUT->str('name')); 893a56750bSAndreas Gohr break; 90a34ef333SKlap-in } 9193d995e0SAndreas Gohr } 9293d995e0SAndreas Gohr 933a56750bSAndreas Gohr /** @inheritdoc */ 943a56750bSAndreas Gohr function html() 953a56750bSAndreas Gohr { 96271f9caeSSzymon Olewniczak global $INPUT; 9793d995e0SAndreas Gohr 9893d995e0SAndreas Gohr echo $this->locale_xhtml('intro'); 993a56750bSAndreas Gohr if (!$this->db) return; 10093d995e0SAndreas Gohr 10193d995e0SAndreas Gohr 1023a56750bSAndreas Gohr echo '<h2>' . $this->getLang('db') . ' "' . hsc($this->db->getDbName()) . '"</h2>'; 10393d995e0SAndreas Gohr echo '<div class="level2">'; 10493d995e0SAndreas Gohr 1053a56750bSAndreas Gohr echo '<div class="commands">'; 1063a56750bSAndreas Gohr $this->showCommands(); 1073a56750bSAndreas Gohr $this->showSavedQueries(); 1083a56750bSAndreas Gohr echo '</div>'; 109a34ef333SKlap-in 1103a56750bSAndreas Gohr // query form 1113a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink()]); 1123a56750bSAndreas Gohr $form->addClass('sqliteplugin'); 113ecc45fdcSSzymon Olewniczak $form->addFieldsetOpen('SQL Command'); 1143a56750bSAndreas Gohr $form->addTextarea('sql')->addClass('edit'); 1153a56750bSAndreas Gohr $form->addButton('submit', $this->getLang('btn_execute'))->attr('type', 'submit'); 116ecc45fdcSSzymon Olewniczak $form->addTextInput('name', $this->getLang('query_name')); 1173a56750bSAndreas Gohr $form->addButton('cmd[save_query]', $this->getLang('save_query'))->attr('type', 'submit'); 118ecc45fdcSSzymon Olewniczak $form->addFieldsetClose(); 1193a56750bSAndreas Gohr echo $form->toHTML(); 12093d995e0SAndreas Gohr 1213a56750bSAndreas Gohr // results 1223a56750bSAndreas Gohr if ($INPUT->has('sql')) $this->showQueryResults($INPUT->str('sql')); 123271f9caeSSzymon Olewniczak echo '</div>'; 124271f9caeSSzymon Olewniczak } 125271f9caeSSzymon Olewniczak 1263a56750bSAndreas Gohr function getTOC() 1273a56750bSAndreas Gohr { 12893d995e0SAndreas Gohr global $conf; 12993d995e0SAndreas Gohr global $ID; 13093d995e0SAndreas Gohr 13193d995e0SAndreas Gohr $toc = array(); 13240698f67SKlap-in $fileextensions = array('sqlite2' => '.sqlite', 'sqlite3' => '.sqlite3'); 13393d995e0SAndreas Gohr 13440698f67SKlap-in foreach ($fileextensions as $dbformat => $fileextension) { 13593d995e0SAndreas Gohr $toc[] = array( 1360542fc8eSKlap-in 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite')), 13740698f67SKlap-in 'title' => $dbformat . ':', 13893d995e0SAndreas Gohr 'level' => 1, 13993d995e0SAndreas Gohr 'type' => 'ul', 14093d995e0SAndreas Gohr ); 14140698f67SKlap-in 14240698f67SKlap-in $dbfiles = glob($conf['metadir'] . '/*' . $fileextension); 14340698f67SKlap-in 14440698f67SKlap-in if (is_array($dbfiles)) foreach ($dbfiles as $file) { 14540698f67SKlap-in $db = basename($file, $fileextension); 14640698f67SKlap-in $toc[] = array( 14784041991SKlap-in 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite', 'db' => $db, 'version' => $dbformat, 'sectok' => getSecurityToken())), 14840698f67SKlap-in 'title' => $this->getLang('db') . ' ' . $db, 14940698f67SKlap-in 'level' => 2, 15040698f67SKlap-in 'type' => 'ul', 15140698f67SKlap-in ); 15240698f67SKlap-in } 15393d995e0SAndreas Gohr } 15493d995e0SAndreas Gohr 15593d995e0SAndreas Gohr return $toc; 15693d995e0SAndreas Gohr } 1573a56750bSAndreas Gohr 1583a56750bSAndreas Gohr /** 1593a56750bSAndreas Gohr * Execute and display the results of the given SQL query 1603a56750bSAndreas Gohr * 1613a56750bSAndreas Gohr * multiple queries can be given separated by semicolons 1623a56750bSAndreas Gohr * 1633a56750bSAndreas Gohr * @param string $sql 1643a56750bSAndreas Gohr */ 1653a56750bSAndreas Gohr protected function showQueryResults($sql) 1663a56750bSAndreas Gohr { 1673a56750bSAndreas Gohr echo '<h3 id="scroll__here">Query results</h3>'; 1683a56750bSAndreas Gohr 1693a56750bSAndreas Gohr $sql = Tools::SQLstring2array($sql); 1703a56750bSAndreas Gohr foreach ($sql as $s) { 1713a56750bSAndreas Gohr $s = preg_replace('!^\s*--.*$!m', '', $s); 1723a56750bSAndreas Gohr $s = trim($s); 1733a56750bSAndreas Gohr if (!$s) continue; 1743a56750bSAndreas Gohr 1753a56750bSAndreas Gohr try { 1763a56750bSAndreas Gohr $time_start = microtime(true); 1773a56750bSAndreas Gohr $result = $this->db->queryAll($s); 1783a56750bSAndreas Gohr $time_end = microtime(true); 1793a56750bSAndreas Gohr } catch (Exception $e) { 1803a56750bSAndreas Gohr msg($e->getMessage(), -1); 1813a56750bSAndreas Gohr continue; 18293d995e0SAndreas Gohr } 18393d995e0SAndreas Gohr 1843a56750bSAndreas Gohr $time = $time_end - $time_start; 1853a56750bSAndreas Gohr $cnt = count($result); 1863a56750bSAndreas Gohr msg($cnt . ' affected rows in ' . $this->microtimeToSeconds($time) . ' seconds', 1); 1873a56750bSAndreas Gohr if (!$cnt) continue; 1883a56750bSAndreas Gohr 1893a56750bSAndreas Gohr echo '<div>'; 1903a56750bSAndreas Gohr $ths = array_keys($result[0]); 1913a56750bSAndreas Gohr echo '<table class="inline">'; 1923a56750bSAndreas Gohr echo '<tr>'; 1933a56750bSAndreas Gohr foreach ($ths as $th) { 1943a56750bSAndreas Gohr echo '<th>' . hsc($th) . '</th>'; 1953a56750bSAndreas Gohr } 1963a56750bSAndreas Gohr echo '</tr>'; 1973a56750bSAndreas Gohr foreach ($result as $row) { 1983a56750bSAndreas Gohr echo '<tr>'; 1993a56750bSAndreas Gohr $tds = array_values($row); 2003a56750bSAndreas Gohr foreach ($tds as $td) { 2013a56750bSAndreas Gohr if ($td === null) $td = '␀'; 2023a56750bSAndreas Gohr echo '<td>' . hsc($td) . '</td>'; 2033a56750bSAndreas Gohr } 2043a56750bSAndreas Gohr echo '</tr>'; 2053a56750bSAndreas Gohr } 2063a56750bSAndreas Gohr echo '</table>'; 2073a56750bSAndreas Gohr echo '</div>'; 2083a56750bSAndreas Gohr } 2093a56750bSAndreas Gohr } 2103a56750bSAndreas Gohr 2113a56750bSAndreas Gohr 2123a56750bSAndreas Gohr /** 2133a56750bSAndreas Gohr * Convert a microtime() value to a string in seconds 2143a56750bSAndreas Gohr * 2153a56750bSAndreas Gohr * @param float $time 2163a56750bSAndreas Gohr * @return string 2173a56750bSAndreas Gohr */ 2183a56750bSAndreas Gohr protected function microtimeToSeconds($time) 2193a56750bSAndreas Gohr { 2203a56750bSAndreas Gohr return ($time < 0.0001 ? substr($time, 0, 5) . substr($time, -3) : substr($time, 0, 7)); 2213a56750bSAndreas Gohr } 2223a56750bSAndreas Gohr 2233a56750bSAndreas Gohr /** 2243a56750bSAndreas Gohr * Construct a link to the sqlite admin page with the given additional parameters 2253a56750bSAndreas Gohr * 2263a56750bSAndreas Gohr * Basically a wrapper around wl() with some defaults 2273a56750bSAndreas Gohr * 2283a56750bSAndreas Gohr * @param string[] $params 2293a56750bSAndreas Gohr * @param bool $form for use in form action? 2303a56750bSAndreas Gohr * @return string 2313a56750bSAndreas Gohr */ 2323a56750bSAndreas Gohr protected function selfLink($form = true, $params = []) 2333a56750bSAndreas Gohr { 2343a56750bSAndreas Gohr global $ID; 2353a56750bSAndreas Gohr $params = array_merge( 2363a56750bSAndreas Gohr [ 2373a56750bSAndreas Gohr 'do' => 'admin', 2383a56750bSAndreas Gohr 'page' => 'sqlite', 2393a56750bSAndreas Gohr 'db' => $this->db ? $this->db->getDBName() : '', 2403a56750bSAndreas Gohr 'sectok' => getSecurityToken(), 2413a56750bSAndreas Gohr ], $params 2423a56750bSAndreas Gohr ); 2433a56750bSAndreas Gohr 2443a56750bSAndreas Gohr return wl($ID, $params, false, $form ? '&' : '&'); 2453a56750bSAndreas Gohr } 2463a56750bSAndreas Gohr 2473a56750bSAndreas Gohr /** 2483a56750bSAndreas Gohr * Display the standard actions for a database 2493a56750bSAndreas Gohr */ 2503a56750bSAndreas Gohr protected function showCommands() 2513a56750bSAndreas Gohr { 2523a56750bSAndreas Gohr $commands = [ 2533a56750bSAndreas Gohr 'dbversion' => [ 2543a56750bSAndreas Gohr 'sql' => 'SELECT val FROM opts WHERE opt=\'dbversion\'', 2553a56750bSAndreas Gohr ], 2563a56750bSAndreas Gohr 'table' => [ 2573a56750bSAndreas Gohr 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'table\' ORDER BY name', 2583a56750bSAndreas Gohr ], 2593a56750bSAndreas Gohr 'index' => [ 2603a56750bSAndreas Gohr 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'index\' ORDER BY name', 2613a56750bSAndreas Gohr ], 2623a56750bSAndreas Gohr 'export' => [ 2633a56750bSAndreas Gohr 'cmd' => 'export' 2643a56750bSAndreas Gohr ], 2653a56750bSAndreas Gohr ]; 2663a56750bSAndreas Gohr 2673a56750bSAndreas Gohr // import form 2683a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink(), 'enctype' => 'multipart/form-data', 'method' => 'post']); 2693a56750bSAndreas Gohr $form->addElement(new InputElement('file', 'importfile')); 2703a56750bSAndreas Gohr $form->addButton('cmd[import]', $this->getLang('import')); 2713a56750bSAndreas Gohr 2723a56750bSAndreas Gohr // output as a list 2733a56750bSAndreas Gohr echo '<ul>'; 2743a56750bSAndreas Gohr foreach ($commands as $label => $command) { 2753a56750bSAndreas Gohr echo '<li><div class="li">'; 2763a56750bSAndreas Gohr echo '<a href="' . $this->selfLink(false, $command) . '">' . $this->getLang($label) . '</a>'; 2773a56750bSAndreas Gohr echo '</div></li>'; 2783a56750bSAndreas Gohr } 2793a56750bSAndreas Gohr echo '<li><div class="li">'; 2803a56750bSAndreas Gohr echo $form->toHTML(); 2813a56750bSAndreas Gohr echo '</div></li>'; 2823a56750bSAndreas Gohr echo '</ul>'; 2833a56750bSAndreas Gohr } 2843a56750bSAndreas Gohr 2853a56750bSAndreas Gohr /** 2863a56750bSAndreas Gohr * FIXME needs to be cleaned up 2873a56750bSAndreas Gohr */ 2883a56750bSAndreas Gohr public function showSavedQueries() 2893a56750bSAndreas Gohr { 2903a56750bSAndreas Gohr $queries = $this->querySaver->getQueries(); 2913a56750bSAndreas Gohr if (!$queries) return; 2923a56750bSAndreas Gohr 2933a56750bSAndreas Gohr echo '<ul>'; 2943a56750bSAndreas Gohr foreach ($queries as $query) { 2953a56750bSAndreas Gohr $link = $this->selfLink(false, ['sql' => $query['sql']]); 2963a56750bSAndreas Gohr $del = $this->selfLink(false, ['cmd' => 'delete_query', 'name' => $query['name']]); 2973a56750bSAndreas Gohr 2983a56750bSAndreas Gohr echo '<li><div class="li">'; 2993a56750bSAndreas Gohr echo '<a href="' . $link . '">' . hsc($query['name']) . '</a>'; 3003a56750bSAndreas Gohr echo ' [<a href="' . $del . '">' . $this->getLang('delete_query') . '</a>]'; 3013a56750bSAndreas Gohr echo '</div></li>'; 3023a56750bSAndreas Gohr } 3033a56750bSAndreas Gohr echo '</ul>'; 3043a56750bSAndreas Gohr } 3053a56750bSAndreas Gohr} 306