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 99038f707SAndreas Gohr/** 109038f707SAndreas Gohr * DokuWiki Plugin sqlite (Admin Component) 119038f707SAndreas Gohr * 129038f707SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 139038f707SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 149038f707SAndreas 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 */ 249038f707SAndreas Gohr public function getMenuSort() 253a56750bSAndreas Gohr { 260542fc8eSKlap-in return 500; 270542fc8eSKlap-in } 280542fc8eSKlap-in 293a56750bSAndreas Gohr /** @inheritdoc */ 309038f707SAndreas Gohr public function forAdminOnly() 313a56750bSAndreas Gohr { 320542fc8eSKlap-in return true; 330542fc8eSKlap-in } 3493d995e0SAndreas Gohr 353a56750bSAndreas Gohr /** @inheritdoc */ 369038f707SAndreas 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': 55*04a1c67aSAnna Dabrowska $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; 89*04a1c67aSAnna Dabrowska case 'download': 90*04a1c67aSAnna Dabrowska $file = $this->db->getDbFile(); 91*04a1c67aSAnna Dabrowska header('Content-Type: application/vnd.sqlite3'); 92*04a1c67aSAnna Dabrowska header('Content-Disposition: attachment; filename="' . $this->db->getDbName() . SQLiteDB::FILE_EXTENSION . '"'); 93*04a1c67aSAnna Dabrowska readfile($file); 94*04a1c67aSAnna Dabrowska exit(0); 95a34ef333SKlap-in } 9693d995e0SAndreas Gohr } 9793d995e0SAndreas Gohr 983a56750bSAndreas Gohr /** @inheritdoc */ 999038f707SAndreas Gohr public function html() 1003a56750bSAndreas Gohr { 101271f9caeSSzymon Olewniczak global $INPUT; 10293d995e0SAndreas Gohr 1039038f707SAndreas Gohr echo '<div class="plugin_sqlite_admin">'; 10493d995e0SAndreas Gohr echo $this->locale_xhtml('intro'); 10593d995e0SAndreas Gohr 1069038f707SAndreas Gohr if ($this->db) { 1073a56750bSAndreas Gohr echo '<h2>' . $this->getLang('db') . ' "' . hsc($this->db->getDbName()) . '"</h2>'; 10893d995e0SAndreas Gohr 1093a56750bSAndreas Gohr echo '<div class="commands">'; 1103a56750bSAndreas Gohr $this->showCommands(); 1113a56750bSAndreas Gohr $this->showSavedQueries(); 1123a56750bSAndreas Gohr echo '</div>'; 113a34ef333SKlap-in 1143a56750bSAndreas Gohr // query form 1153a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink()]); 1163a56750bSAndreas Gohr $form->addClass('sqliteplugin'); 117ecc45fdcSSzymon Olewniczak $form->addFieldsetOpen('SQL Command'); 1183a56750bSAndreas Gohr $form->addTextarea('sql')->addClass('edit'); 1193a56750bSAndreas Gohr $form->addButton('submit', $this->getLang('btn_execute'))->attr('type', 'submit'); 120ecc45fdcSSzymon Olewniczak $form->addTextInput('name', $this->getLang('query_name')); 1213a56750bSAndreas Gohr $form->addButton('cmd[save_query]', $this->getLang('save_query'))->attr('type', 'submit'); 122ecc45fdcSSzymon Olewniczak $form->addFieldsetClose(); 1233a56750bSAndreas Gohr echo $form->toHTML(); 12493d995e0SAndreas Gohr 1253a56750bSAndreas Gohr // results 1263a56750bSAndreas Gohr if ($INPUT->has('sql')) $this->showQueryResults($INPUT->str('sql')); 1279038f707SAndreas Gohr } 128271f9caeSSzymon Olewniczak echo '</div>'; 129271f9caeSSzymon Olewniczak } 130271f9caeSSzymon Olewniczak 1315a212f8eSAndreas Gohr /** 1325a212f8eSAndreas Gohr * List all available databases in the TOC 1335a212f8eSAndreas Gohr * 1345a212f8eSAndreas Gohr * @inheritdoc 1355a212f8eSAndreas Gohr */ 1365a212f8eSAndreas Gohr public function getTOC() 1373a56750bSAndreas Gohr { 13893d995e0SAndreas Gohr global $conf; 13993d995e0SAndreas Gohr global $ID; 14093d995e0SAndreas Gohr 1415a212f8eSAndreas Gohr $toc = []; 1425a212f8eSAndreas Gohr $toc[] = [ 1435a212f8eSAndreas Gohr 'link' => wl($ID, ['do' => 'admin', 'page' => 'sqlite']), 1445a212f8eSAndreas Gohr 'title' => $this->getLang('db') . ':', 14593d995e0SAndreas Gohr 'level' => 1, 14693d995e0SAndreas Gohr 'type' => 'ul', 1475a212f8eSAndreas Gohr ]; 1485a212f8eSAndreas Gohr $dbfiles = glob($conf['metadir'] . '/*.sqlite3'); 14940698f67SKlap-in if (is_array($dbfiles)) foreach ($dbfiles as $file) { 1505a212f8eSAndreas Gohr $db = basename($file, '.sqlite3'); 15140698f67SKlap-in $toc[] = array( 1525a212f8eSAndreas Gohr 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite', 'db' => $db, 'sectok' => getSecurityToken())), 1535a212f8eSAndreas Gohr 'title' => $db, 15440698f67SKlap-in 'level' => 2, 15540698f67SKlap-in 'type' => 'ul', 15640698f67SKlap-in ); 15740698f67SKlap-in } 15893d995e0SAndreas Gohr 15993d995e0SAndreas Gohr return $toc; 16093d995e0SAndreas Gohr } 1613a56750bSAndreas Gohr 1623a56750bSAndreas Gohr /** 1633a56750bSAndreas Gohr * Execute and display the results of the given SQL query 1643a56750bSAndreas Gohr * 1653a56750bSAndreas Gohr * multiple queries can be given separated by semicolons 1663a56750bSAndreas Gohr * 1673a56750bSAndreas Gohr * @param string $sql 1683a56750bSAndreas Gohr */ 1693a56750bSAndreas Gohr protected function showQueryResults($sql) 1703a56750bSAndreas Gohr { 1713a56750bSAndreas Gohr echo '<h3 id="scroll__here">Query results</h3>'; 1723a56750bSAndreas Gohr 1733a56750bSAndreas Gohr $sql = Tools::SQLstring2array($sql); 1743a56750bSAndreas Gohr foreach ($sql as $s) { 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 ], 265*04a1c67aSAnna Dabrowska 'download' => [ 266*04a1c67aSAnna Dabrowska 'cmd' => 'download' 267*04a1c67aSAnna Dabrowska ], 2683a56750bSAndreas Gohr ]; 2693a56750bSAndreas Gohr 2703a56750bSAndreas Gohr // import form 2713a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink(), 'enctype' => 'multipart/form-data', 'method' => 'post']); 2729038f707SAndreas Gohr $form->addElement( 2739038f707SAndreas Gohr (new InputElement('file', 'importfile')) 2749038f707SAndreas Gohr ->attr('required', 'required') 2759038f707SAndreas Gohr ->attr('accept', '.sql') 2769038f707SAndreas Gohr ); 2773a56750bSAndreas Gohr $form->addButton('cmd[import]', $this->getLang('import')); 2783a56750bSAndreas Gohr 2793a56750bSAndreas Gohr // output as a list 2803a56750bSAndreas Gohr echo '<ul>'; 2813a56750bSAndreas Gohr foreach ($commands as $label => $command) { 2823a56750bSAndreas Gohr echo '<li><div class="li">'; 2833a56750bSAndreas Gohr echo '<a href="' . $this->selfLink(false, $command) . '">' . $this->getLang($label) . '</a>'; 2843a56750bSAndreas Gohr echo '</div></li>'; 2853a56750bSAndreas Gohr } 2863a56750bSAndreas Gohr echo '<li><div class="li">'; 2873a56750bSAndreas Gohr echo $form->toHTML(); 2883a56750bSAndreas Gohr echo '</div></li>'; 2893a56750bSAndreas Gohr echo '</ul>'; 2903a56750bSAndreas Gohr } 2913a56750bSAndreas Gohr 2923a56750bSAndreas Gohr /** 2939038f707SAndreas Gohr * Display the saved queries for this database 2943a56750bSAndreas Gohr */ 2953a56750bSAndreas Gohr public function showSavedQueries() 2963a56750bSAndreas Gohr { 2973a56750bSAndreas Gohr $queries = $this->querySaver->getQueries(); 2983a56750bSAndreas Gohr if (!$queries) return; 2993a56750bSAndreas Gohr 3003a56750bSAndreas Gohr echo '<ul>'; 3013a56750bSAndreas Gohr foreach ($queries as $query) { 3023a56750bSAndreas Gohr $link = $this->selfLink(false, ['sql' => $query['sql']]); 3033a56750bSAndreas Gohr $del = $this->selfLink(false, ['cmd' => 'delete_query', 'name' => $query['name']]); 3043a56750bSAndreas Gohr 3053a56750bSAndreas Gohr echo '<li><div class="li">'; 3063a56750bSAndreas Gohr echo '<a href="' . $link . '">' . hsc($query['name']) . '</a>'; 3073a56750bSAndreas Gohr echo ' [<a href="' . $del . '">' . $this->getLang('delete_query') . '</a>]'; 3083a56750bSAndreas Gohr echo '</div></li>'; 3093a56750bSAndreas Gohr } 3103a56750bSAndreas Gohr echo '</ul>'; 3113a56750bSAndreas Gohr } 3123a56750bSAndreas Gohr} 313