193d995e0SAndreas Gohr<?php 293d995e0SAndreas Gohr 3af0e7691SSzymon Olewniczakuse dokuwiki\Form\Form; 4ecc45fdcSSzymon Olewniczakuse dokuwiki\Form\InputElement; 53a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\QuerySaver; 63a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 73a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\Tools; 8af0e7691SSzymon Olewniczak 9*9038f707SAndreas Gohr/** 10*9038f707SAndreas Gohr * DokuWiki Plugin sqlite (Admin Component) 11*9038f707SAndreas Gohr * 12*9038f707SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 13*9038f707SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 14*9038f707SAndreas Gohr */ 153a56750bSAndreas Gohrclass admin_plugin_sqlite extends DokuWiki_Admin_Plugin 163a56750bSAndreas Gohr{ 173a56750bSAndreas Gohr /** @var SQLiteDB */ 183a56750bSAndreas Gohr protected $db = null; 1993d995e0SAndreas Gohr 203a56750bSAndreas Gohr /** @var QuerySaver */ 213a56750bSAndreas Gohr protected $querySaver = null; 223a56750bSAndreas Gohr 233a56750bSAndreas Gohr /** @inheritdoc */ 24*9038f707SAndreas Gohr public function getMenuSort() 253a56750bSAndreas Gohr { 260542fc8eSKlap-in return 500; 270542fc8eSKlap-in } 280542fc8eSKlap-in 293a56750bSAndreas Gohr /** @inheritdoc */ 30*9038f707SAndreas Gohr public function forAdminOnly() 313a56750bSAndreas Gohr { 320542fc8eSKlap-in return true; 330542fc8eSKlap-in } 3493d995e0SAndreas Gohr 353a56750bSAndreas Gohr /** @inheritdoc */ 36*9038f707SAndreas Gohr public function handle() 373a56750bSAndreas Gohr { 38a34ef333SKlap-in global $conf; 390dbd1d4cSSzymon Olewniczak global $INPUT; 400dbd1d4cSSzymon Olewniczak 413a56750bSAndreas Gohr // load database if given and security token is valid 423a56750bSAndreas Gohr if ($INPUT->str('db') && checkSecurityToken()) { 433a56750bSAndreas Gohr try { 443a56750bSAndreas Gohr $this->db = new SQLiteDB($INPUT->str('db'), ''); 453a56750bSAndreas Gohr $this->querySaver = new QuerySaver($this->db->getDBName()); 463a56750bSAndreas Gohr } catch (Exception $e) { 473a56750bSAndreas Gohr msg($e->getMessage(), -1); 483a56750bSAndreas Gohr return; 49a34ef333SKlap-in } 503a56750bSAndreas Gohr } 510dbd1d4cSSzymon Olewniczak 523a56750bSAndreas Gohr $cmd = $INPUT->extract('cmd')->str('cmd'); 533a56750bSAndreas Gohr switch ($cmd) { 543a56750bSAndreas Gohr case 'export': 553a56750bSAndreas Gohr $exportfile = $conf['tmpdir'] . '/' . $this->db->getDBName() . '.sql'; 563a56750bSAndreas Gohr $this->db->dumpToFile($exportfile); 570dbd1d4cSSzymon Olewniczak header('Content-Type: text/sql'); 583a56750bSAndreas Gohr header('Content-Disposition: attachment; filename="' . $this->db->getDbName() . '.sql";'); 593a56750bSAndreas Gohr readfile($exportfile); 603a56750bSAndreas Gohr unlink($exportfile); 610dbd1d4cSSzymon Olewniczak exit(0); 623a56750bSAndreas Gohr case 'import': 633a56750bSAndreas Gohr $importfile = $_FILES['importfile']['tmp_name']; 640dbd1d4cSSzymon Olewniczak 653a56750bSAndreas Gohr if (empty($importfile)) { 660dbd1d4cSSzymon Olewniczak msg($this->getLang('import_no_file'), -1); 670dbd1d4cSSzymon Olewniczak return; 680dbd1d4cSSzymon Olewniczak } 690dbd1d4cSSzymon Olewniczak 707ddaad11SAndreas Gohr $sql = Tools::SQLstring2array(file_get_contents($importfile)); 713a56750bSAndreas Gohr try { 7214e7409bSAndreas Gohr $this->db->getPdo()->beginTransaction(); 737ddaad11SAndreas Gohr foreach ($sql as $s) { 747ddaad11SAndreas Gohr $this->db->exec($s); 757ddaad11SAndreas Gohr } 7614e7409bSAndreas Gohr $this->db->getPdo()->commit(); 770dbd1d4cSSzymon Olewniczak msg($this->getLang('import_success'), 1); 783a56750bSAndreas Gohr } catch (Exception $e) { 7914e7409bSAndreas Gohr $this->db->getPdo()->rollBack(); 807ddaad11SAndreas Gohr msg(hsc($e->getMessage()), -1); 810dbd1d4cSSzymon Olewniczak } 823a56750bSAndreas Gohr break; 833a56750bSAndreas Gohr case 'save_query': 843a56750bSAndreas Gohr $this->querySaver->saveQuery($INPUT->str('name'), $INPUT->str('sql')); 853a56750bSAndreas Gohr break; 863a56750bSAndreas Gohr case 'delete_query': 873a56750bSAndreas Gohr $this->querySaver->deleteQuery($INPUT->str('name')); 883a56750bSAndreas Gohr break; 89a34ef333SKlap-in } 9093d995e0SAndreas Gohr } 9193d995e0SAndreas Gohr 923a56750bSAndreas Gohr /** @inheritdoc */ 93*9038f707SAndreas Gohr public function html() 943a56750bSAndreas Gohr { 95271f9caeSSzymon Olewniczak global $INPUT; 9693d995e0SAndreas Gohr 97*9038f707SAndreas Gohr echo '<div class="plugin_sqlite_admin">'; 9893d995e0SAndreas Gohr echo $this->locale_xhtml('intro'); 9993d995e0SAndreas Gohr 100*9038f707SAndreas Gohr if ($this->db) { 1013a56750bSAndreas Gohr echo '<h2>' . $this->getLang('db') . ' "' . hsc($this->db->getDbName()) . '"</h2>'; 10293d995e0SAndreas Gohr 1033a56750bSAndreas Gohr echo '<div class="commands">'; 1043a56750bSAndreas Gohr $this->showCommands(); 1053a56750bSAndreas Gohr $this->showSavedQueries(); 1063a56750bSAndreas Gohr echo '</div>'; 107a34ef333SKlap-in 1083a56750bSAndreas Gohr // query form 1093a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink()]); 1103a56750bSAndreas Gohr $form->addClass('sqliteplugin'); 111ecc45fdcSSzymon Olewniczak $form->addFieldsetOpen('SQL Command'); 1123a56750bSAndreas Gohr $form->addTextarea('sql')->addClass('edit'); 1133a56750bSAndreas Gohr $form->addButton('submit', $this->getLang('btn_execute'))->attr('type', 'submit'); 114ecc45fdcSSzymon Olewniczak $form->addTextInput('name', $this->getLang('query_name')); 1153a56750bSAndreas Gohr $form->addButton('cmd[save_query]', $this->getLang('save_query'))->attr('type', 'submit'); 116ecc45fdcSSzymon Olewniczak $form->addFieldsetClose(); 1173a56750bSAndreas Gohr echo $form->toHTML(); 11893d995e0SAndreas Gohr 1193a56750bSAndreas Gohr // results 1203a56750bSAndreas Gohr if ($INPUT->has('sql')) $this->showQueryResults($INPUT->str('sql')); 121*9038f707SAndreas Gohr } 122271f9caeSSzymon Olewniczak echo '</div>'; 123271f9caeSSzymon Olewniczak } 124271f9caeSSzymon Olewniczak 1255a212f8eSAndreas Gohr /** 1265a212f8eSAndreas Gohr * List all available databases in the TOC 1275a212f8eSAndreas Gohr * 1285a212f8eSAndreas Gohr * @inheritdoc 1295a212f8eSAndreas Gohr */ 1305a212f8eSAndreas Gohr public function getTOC() 1313a56750bSAndreas Gohr { 13293d995e0SAndreas Gohr global $conf; 13393d995e0SAndreas Gohr global $ID; 13493d995e0SAndreas Gohr 1355a212f8eSAndreas Gohr $toc = []; 1365a212f8eSAndreas Gohr $toc[] = [ 1375a212f8eSAndreas Gohr 'link' => wl($ID, ['do' => 'admin', 'page' => 'sqlite']), 1385a212f8eSAndreas Gohr 'title' => $this->getLang('db') . ':', 13993d995e0SAndreas Gohr 'level' => 1, 14093d995e0SAndreas Gohr 'type' => 'ul', 1415a212f8eSAndreas Gohr ]; 1425a212f8eSAndreas Gohr $dbfiles = glob($conf['metadir'] . '/*.sqlite3'); 14340698f67SKlap-in if (is_array($dbfiles)) foreach ($dbfiles as $file) { 1445a212f8eSAndreas Gohr $db = basename($file, '.sqlite3'); 14540698f67SKlap-in $toc[] = array( 1465a212f8eSAndreas Gohr 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite', 'db' => $db, 'sectok' => getSecurityToken())), 1475a212f8eSAndreas Gohr 'title' => $db, 14840698f67SKlap-in 'level' => 2, 14940698f67SKlap-in 'type' => 'ul', 15040698f67SKlap-in ); 15140698f67SKlap-in } 15293d995e0SAndreas Gohr 15393d995e0SAndreas Gohr return $toc; 15493d995e0SAndreas Gohr } 1553a56750bSAndreas Gohr 1563a56750bSAndreas Gohr /** 1573a56750bSAndreas Gohr * Execute and display the results of the given SQL query 1583a56750bSAndreas Gohr * 1593a56750bSAndreas Gohr * multiple queries can be given separated by semicolons 1603a56750bSAndreas Gohr * 1613a56750bSAndreas Gohr * @param string $sql 1623a56750bSAndreas Gohr */ 1633a56750bSAndreas Gohr protected function showQueryResults($sql) 1643a56750bSAndreas Gohr { 1653a56750bSAndreas Gohr echo '<h3 id="scroll__here">Query results</h3>'; 1663a56750bSAndreas Gohr 1673a56750bSAndreas Gohr $sql = Tools::SQLstring2array($sql); 1683a56750bSAndreas Gohr foreach ($sql as $s) { 1693a56750bSAndreas Gohr try { 1703a56750bSAndreas Gohr $time_start = microtime(true); 1713a56750bSAndreas Gohr $result = $this->db->queryAll($s); 1723a56750bSAndreas Gohr $time_end = microtime(true); 1733a56750bSAndreas Gohr } catch (Exception $e) { 1743a56750bSAndreas Gohr msg($e->getMessage(), -1); 1753a56750bSAndreas Gohr continue; 17693d995e0SAndreas Gohr } 17793d995e0SAndreas Gohr 1783a56750bSAndreas Gohr $time = $time_end - $time_start; 1793a56750bSAndreas Gohr $cnt = count($result); 1803a56750bSAndreas Gohr msg($cnt . ' affected rows in ' . $this->microtimeToSeconds($time) . ' seconds', 1); 1813a56750bSAndreas Gohr if (!$cnt) continue; 1823a56750bSAndreas Gohr 1833a56750bSAndreas Gohr echo '<div>'; 1843a56750bSAndreas Gohr $ths = array_keys($result[0]); 1853a56750bSAndreas Gohr echo '<table class="inline">'; 1863a56750bSAndreas Gohr echo '<tr>'; 1873a56750bSAndreas Gohr foreach ($ths as $th) { 1883a56750bSAndreas Gohr echo '<th>' . hsc($th) . '</th>'; 1893a56750bSAndreas Gohr } 1903a56750bSAndreas Gohr echo '</tr>'; 1913a56750bSAndreas Gohr foreach ($result as $row) { 1923a56750bSAndreas Gohr echo '<tr>'; 1933a56750bSAndreas Gohr $tds = array_values($row); 1943a56750bSAndreas Gohr foreach ($tds as $td) { 1953a56750bSAndreas Gohr if ($td === null) $td = '␀'; 1963a56750bSAndreas Gohr echo '<td>' . hsc($td) . '</td>'; 1973a56750bSAndreas Gohr } 1983a56750bSAndreas Gohr echo '</tr>'; 1993a56750bSAndreas Gohr } 2003a56750bSAndreas Gohr echo '</table>'; 2013a56750bSAndreas Gohr echo '</div>'; 2023a56750bSAndreas Gohr } 2033a56750bSAndreas Gohr } 2043a56750bSAndreas Gohr 2053a56750bSAndreas Gohr 2063a56750bSAndreas Gohr /** 2073a56750bSAndreas Gohr * Convert a microtime() value to a string in seconds 2083a56750bSAndreas Gohr * 2093a56750bSAndreas Gohr * @param float $time 2103a56750bSAndreas Gohr * @return string 2113a56750bSAndreas Gohr */ 2123a56750bSAndreas Gohr protected function microtimeToSeconds($time) 2133a56750bSAndreas Gohr { 2143a56750bSAndreas Gohr return ($time < 0.0001 ? substr($time, 0, 5) . substr($time, -3) : substr($time, 0, 7)); 2153a56750bSAndreas Gohr } 2163a56750bSAndreas Gohr 2173a56750bSAndreas Gohr /** 2183a56750bSAndreas Gohr * Construct a link to the sqlite admin page with the given additional parameters 2193a56750bSAndreas Gohr * 2203a56750bSAndreas Gohr * Basically a wrapper around wl() with some defaults 2213a56750bSAndreas Gohr * 2223a56750bSAndreas Gohr * @param string[] $params 2233a56750bSAndreas Gohr * @param bool $form for use in form action? 2243a56750bSAndreas Gohr * @return string 2253a56750bSAndreas Gohr */ 2263a56750bSAndreas Gohr protected function selfLink($form = true, $params = []) 2273a56750bSAndreas Gohr { 2283a56750bSAndreas Gohr global $ID; 2293a56750bSAndreas Gohr $params = array_merge( 2303a56750bSAndreas Gohr [ 2313a56750bSAndreas Gohr 'do' => 'admin', 2323a56750bSAndreas Gohr 'page' => 'sqlite', 2333a56750bSAndreas Gohr 'db' => $this->db ? $this->db->getDBName() : '', 2343a56750bSAndreas Gohr 'sectok' => getSecurityToken(), 2353a56750bSAndreas Gohr ], $params 2363a56750bSAndreas Gohr ); 2373a56750bSAndreas Gohr 2383a56750bSAndreas Gohr return wl($ID, $params, false, $form ? '&' : '&'); 2393a56750bSAndreas Gohr } 2403a56750bSAndreas Gohr 2413a56750bSAndreas Gohr /** 2423a56750bSAndreas Gohr * Display the standard actions for a database 2433a56750bSAndreas Gohr */ 2443a56750bSAndreas Gohr protected function showCommands() 2453a56750bSAndreas Gohr { 2463a56750bSAndreas Gohr $commands = [ 2473a56750bSAndreas Gohr 'dbversion' => [ 2483a56750bSAndreas Gohr 'sql' => 'SELECT val FROM opts WHERE opt=\'dbversion\'', 2493a56750bSAndreas Gohr ], 2503a56750bSAndreas Gohr 'table' => [ 2513a56750bSAndreas Gohr 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'table\' ORDER BY name', 2523a56750bSAndreas Gohr ], 2533a56750bSAndreas Gohr 'index' => [ 2543a56750bSAndreas Gohr 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'index\' ORDER BY name', 2553a56750bSAndreas Gohr ], 2563a56750bSAndreas Gohr 'export' => [ 2573a56750bSAndreas Gohr 'cmd' => 'export' 2583a56750bSAndreas Gohr ], 2593a56750bSAndreas Gohr ]; 2603a56750bSAndreas Gohr 2613a56750bSAndreas Gohr // import form 2623a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink(), 'enctype' => 'multipart/form-data', 'method' => 'post']); 263*9038f707SAndreas Gohr $form->addElement( 264*9038f707SAndreas Gohr (new InputElement('file', 'importfile')) 265*9038f707SAndreas Gohr ->attr('required', 'required') 266*9038f707SAndreas Gohr ->attr('accept', '.sql') 267*9038f707SAndreas Gohr ); 2683a56750bSAndreas Gohr $form->addButton('cmd[import]', $this->getLang('import')); 2693a56750bSAndreas Gohr 2703a56750bSAndreas Gohr // output as a list 2713a56750bSAndreas Gohr echo '<ul>'; 2723a56750bSAndreas Gohr foreach ($commands as $label => $command) { 2733a56750bSAndreas Gohr echo '<li><div class="li">'; 2743a56750bSAndreas Gohr echo '<a href="' . $this->selfLink(false, $command) . '">' . $this->getLang($label) . '</a>'; 2753a56750bSAndreas Gohr echo '</div></li>'; 2763a56750bSAndreas Gohr } 2773a56750bSAndreas Gohr echo '<li><div class="li">'; 2783a56750bSAndreas Gohr echo $form->toHTML(); 2793a56750bSAndreas Gohr echo '</div></li>'; 2803a56750bSAndreas Gohr echo '</ul>'; 2813a56750bSAndreas Gohr } 2823a56750bSAndreas Gohr 2833a56750bSAndreas Gohr /** 284*9038f707SAndreas Gohr * Display the saved queries for this database 2853a56750bSAndreas Gohr */ 2863a56750bSAndreas Gohr public function showSavedQueries() 2873a56750bSAndreas Gohr { 2883a56750bSAndreas Gohr $queries = $this->querySaver->getQueries(); 2893a56750bSAndreas Gohr if (!$queries) return; 2903a56750bSAndreas Gohr 2913a56750bSAndreas Gohr echo '<ul>'; 2923a56750bSAndreas Gohr foreach ($queries as $query) { 2933a56750bSAndreas Gohr $link = $this->selfLink(false, ['sql' => $query['sql']]); 2943a56750bSAndreas Gohr $del = $this->selfLink(false, ['cmd' => 'delete_query', 'name' => $query['name']]); 2953a56750bSAndreas Gohr 2963a56750bSAndreas Gohr echo '<li><div class="li">'; 2973a56750bSAndreas Gohr echo '<a href="' . $link . '">' . hsc($query['name']) . '</a>'; 2983a56750bSAndreas Gohr echo ' [<a href="' . $del . '">' . $this->getLang('delete_query') . '</a>]'; 2993a56750bSAndreas Gohr echo '</div></li>'; 3003a56750bSAndreas Gohr } 3013a56750bSAndreas Gohr echo '</ul>'; 3023a56750bSAndreas Gohr } 3033a56750bSAndreas Gohr} 304