193d995e0SAndreas Gohr<?php 293d995e0SAndreas Gohr 3a7a40fb2SAnna Dabrowskause dokuwiki\Extension\AdminPlugin; 4af0e7691SSzymon Olewniczakuse dokuwiki\Form\Form; 5ecc45fdcSSzymon Olewniczakuse dokuwiki\Form\InputElement; 63a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\QuerySaver; 73a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 83a56750bSAndreas Gohruse dokuwiki\plugin\sqlite\Tools; 9af0e7691SSzymon Olewniczak 109038f707SAndreas Gohr/** 119038f707SAndreas Gohr * DokuWiki Plugin sqlite (Admin Component) 129038f707SAndreas Gohr * 139038f707SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 149038f707SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 159038f707SAndreas Gohr */ 16a7a40fb2SAnna Dabrowskaclass admin_plugin_sqlite extends AdminPlugin 173a56750bSAndreas Gohr{ 183a56750bSAndreas Gohr /** @var SQLiteDB */ 19a7a40fb2SAnna Dabrowska protected $db; 2093d995e0SAndreas Gohr 213a56750bSAndreas Gohr /** @var QuerySaver */ 22a7a40fb2SAnna Dabrowska protected $querySaver; 233a56750bSAndreas Gohr 243a56750bSAndreas Gohr /** @inheritdoc */ 259038f707SAndreas Gohr public function getMenuSort() 263a56750bSAndreas Gohr { 270542fc8eSKlap-in return 500; 280542fc8eSKlap-in } 290542fc8eSKlap-in 303a56750bSAndreas Gohr /** @inheritdoc */ 319038f707SAndreas Gohr public function forAdminOnly() 323a56750bSAndreas Gohr { 330542fc8eSKlap-in return true; 340542fc8eSKlap-in } 3593d995e0SAndreas Gohr 363a56750bSAndreas Gohr /** @inheritdoc */ 379038f707SAndreas Gohr public 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': 5604a1c67aSAnna Dabrowska $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 717ddaad11SAndreas Gohr $sql = Tools::SQLstring2array(file_get_contents($importfile)); 723a56750bSAndreas Gohr try { 7314e7409bSAndreas Gohr $this->db->getPdo()->beginTransaction(); 747ddaad11SAndreas Gohr foreach ($sql as $s) { 757ddaad11SAndreas Gohr $this->db->exec($s); 767ddaad11SAndreas Gohr } 7714e7409bSAndreas Gohr $this->db->getPdo()->commit(); 780dbd1d4cSSzymon Olewniczak msg($this->getLang('import_success'), 1); 793a56750bSAndreas Gohr } catch (Exception $e) { 8014e7409bSAndreas Gohr $this->db->getPdo()->rollBack(); 817ddaad11SAndreas 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; 9004a1c67aSAnna Dabrowska case 'download': 9104a1c67aSAnna Dabrowska $file = $this->db->getDbFile(); 9204a1c67aSAnna Dabrowska header('Content-Type: application/vnd.sqlite3'); 93*14fe8e27SAnna Dabrowska header( 94*14fe8e27SAnna Dabrowska 'Content-Disposition: attachment; filename="' 95*14fe8e27SAnna Dabrowska . $this->db->getDbName() . SQLiteDB::FILE_EXTENSION . '"' 96*14fe8e27SAnna Dabrowska ); 9704a1c67aSAnna Dabrowska readfile($file); 9804a1c67aSAnna Dabrowska exit(0); 99a34ef333SKlap-in } 10093d995e0SAndreas Gohr } 10193d995e0SAndreas Gohr 1023a56750bSAndreas Gohr /** @inheritdoc */ 1039038f707SAndreas Gohr public function html() 1043a56750bSAndreas Gohr { 105271f9caeSSzymon Olewniczak global $INPUT; 10693d995e0SAndreas Gohr 1079038f707SAndreas Gohr echo '<div class="plugin_sqlite_admin">'; 10893d995e0SAndreas Gohr echo $this->locale_xhtml('intro'); 10993d995e0SAndreas Gohr 1109038f707SAndreas Gohr if ($this->db) { 1113a56750bSAndreas Gohr echo '<h2>' . $this->getLang('db') . ' "' . hsc($this->db->getDbName()) . '"</h2>'; 11293d995e0SAndreas Gohr 1133a56750bSAndreas Gohr echo '<div class="commands">'; 1143a56750bSAndreas Gohr $this->showCommands(); 1153a56750bSAndreas Gohr $this->showSavedQueries(); 1163a56750bSAndreas Gohr echo '</div>'; 117a34ef333SKlap-in 1183a56750bSAndreas Gohr // query form 1193a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink()]); 1203a56750bSAndreas Gohr $form->addClass('sqliteplugin'); 121ecc45fdcSSzymon Olewniczak $form->addFieldsetOpen('SQL Command'); 1223a56750bSAndreas Gohr $form->addTextarea('sql')->addClass('edit'); 1233a56750bSAndreas Gohr $form->addButton('submit', $this->getLang('btn_execute'))->attr('type', 'submit'); 124ecc45fdcSSzymon Olewniczak $form->addTextInput('name', $this->getLang('query_name')); 1253a56750bSAndreas Gohr $form->addButton('cmd[save_query]', $this->getLang('save_query'))->attr('type', 'submit'); 126ecc45fdcSSzymon Olewniczak $form->addFieldsetClose(); 1273a56750bSAndreas Gohr echo $form->toHTML(); 12893d995e0SAndreas Gohr 1293a56750bSAndreas Gohr // results 1303a56750bSAndreas Gohr if ($INPUT->has('sql')) $this->showQueryResults($INPUT->str('sql')); 1319038f707SAndreas Gohr } 132271f9caeSSzymon Olewniczak echo '</div>'; 133271f9caeSSzymon Olewniczak } 134271f9caeSSzymon Olewniczak 1355a212f8eSAndreas Gohr /** 1365a212f8eSAndreas Gohr * List all available databases in the TOC 1375a212f8eSAndreas Gohr * 1385a212f8eSAndreas Gohr * @inheritdoc 1395a212f8eSAndreas Gohr */ 1405a212f8eSAndreas Gohr public function getTOC() 1413a56750bSAndreas Gohr { 14293d995e0SAndreas Gohr global $conf; 14393d995e0SAndreas Gohr global $ID; 14493d995e0SAndreas Gohr 1455a212f8eSAndreas Gohr $toc = []; 1465a212f8eSAndreas Gohr $toc[] = [ 1475a212f8eSAndreas Gohr 'link' => wl($ID, ['do' => 'admin', 'page' => 'sqlite']), 1485a212f8eSAndreas Gohr 'title' => $this->getLang('db') . ':', 14993d995e0SAndreas Gohr 'level' => 1, 15093d995e0SAndreas Gohr 'type' => 'ul', 1515a212f8eSAndreas Gohr ]; 1525a212f8eSAndreas Gohr $dbfiles = glob($conf['metadir'] . '/*.sqlite3'); 15340698f67SKlap-in if (is_array($dbfiles)) foreach ($dbfiles as $file) { 1545a212f8eSAndreas Gohr $db = basename($file, '.sqlite3'); 155*14fe8e27SAnna Dabrowska $toc[] = [ 156*14fe8e27SAnna Dabrowska 'link' => wl($ID, ['do' => 'admin', 'page' => 'sqlite', 'db' => $db, 'sectok' => getSecurityToken()]), 157*14fe8e27SAnna Dabrowska 'title' => $db, 158*14fe8e27SAnna Dabrowska 'level' => 2, 159*14fe8e27SAnna Dabrowska 'type' => 'ul', 160*14fe8e27SAnna Dabrowska ]; 16140698f67SKlap-in } 16293d995e0SAndreas Gohr 16393d995e0SAndreas Gohr return $toc; 16493d995e0SAndreas Gohr } 1653a56750bSAndreas Gohr 1663a56750bSAndreas Gohr /** 1673a56750bSAndreas Gohr * Execute and display the results of the given SQL query 1683a56750bSAndreas Gohr * 1693a56750bSAndreas Gohr * multiple queries can be given separated by semicolons 1703a56750bSAndreas Gohr * 1713a56750bSAndreas Gohr * @param string $sql 1723a56750bSAndreas Gohr */ 1733a56750bSAndreas Gohr protected function showQueryResults($sql) 1743a56750bSAndreas Gohr { 1753a56750bSAndreas Gohr echo '<h3 id="scroll__here">Query results</h3>'; 1763a56750bSAndreas Gohr 1773a56750bSAndreas Gohr $sql = Tools::SQLstring2array($sql); 1783a56750bSAndreas Gohr foreach ($sql as $s) { 1793a56750bSAndreas Gohr try { 1803a56750bSAndreas Gohr $time_start = microtime(true); 1813a56750bSAndreas Gohr $result = $this->db->queryAll($s); 1823a56750bSAndreas Gohr $time_end = microtime(true); 1833a56750bSAndreas Gohr } catch (Exception $e) { 1843a56750bSAndreas Gohr msg($e->getMessage(), -1); 1853a56750bSAndreas Gohr continue; 18693d995e0SAndreas Gohr } 18793d995e0SAndreas Gohr 1883a56750bSAndreas Gohr $time = $time_end - $time_start; 1893a56750bSAndreas Gohr $cnt = count($result); 1903a56750bSAndreas Gohr msg($cnt . ' affected rows in ' . $this->microtimeToSeconds($time) . ' seconds', 1); 1913a56750bSAndreas Gohr if (!$cnt) continue; 1923a56750bSAndreas Gohr 1933a56750bSAndreas Gohr echo '<div>'; 1943a56750bSAndreas Gohr $ths = array_keys($result[0]); 1953a56750bSAndreas Gohr echo '<table class="inline">'; 1963a56750bSAndreas Gohr echo '<tr>'; 1973a56750bSAndreas Gohr foreach ($ths as $th) { 1983a56750bSAndreas Gohr echo '<th>' . hsc($th) . '</th>'; 1993a56750bSAndreas Gohr } 2003a56750bSAndreas Gohr echo '</tr>'; 2013a56750bSAndreas Gohr foreach ($result as $row) { 2023a56750bSAndreas Gohr echo '<tr>'; 2033a56750bSAndreas Gohr $tds = array_values($row); 2043a56750bSAndreas Gohr foreach ($tds as $td) { 2053a56750bSAndreas Gohr if ($td === null) $td = '␀'; 2063a56750bSAndreas Gohr echo '<td>' . hsc($td) . '</td>'; 2073a56750bSAndreas Gohr } 2083a56750bSAndreas Gohr echo '</tr>'; 2093a56750bSAndreas Gohr } 2103a56750bSAndreas Gohr echo '</table>'; 2113a56750bSAndreas Gohr echo '</div>'; 2123a56750bSAndreas Gohr } 2133a56750bSAndreas Gohr } 2143a56750bSAndreas Gohr 2153a56750bSAndreas Gohr 2163a56750bSAndreas Gohr /** 2173a56750bSAndreas Gohr * Convert a microtime() value to a string in seconds 2183a56750bSAndreas Gohr * 2193a56750bSAndreas Gohr * @param float $time 2203a56750bSAndreas Gohr * @return string 2213a56750bSAndreas Gohr */ 2223a56750bSAndreas Gohr protected function microtimeToSeconds($time) 2233a56750bSAndreas Gohr { 2243a56750bSAndreas Gohr return ($time < 0.0001 ? substr($time, 0, 5) . substr($time, -3) : substr($time, 0, 7)); 2253a56750bSAndreas Gohr } 2263a56750bSAndreas Gohr 2273a56750bSAndreas Gohr /** 2283a56750bSAndreas Gohr * Construct a link to the sqlite admin page with the given additional parameters 2293a56750bSAndreas Gohr * 2303a56750bSAndreas Gohr * Basically a wrapper around wl() with some defaults 2313a56750bSAndreas Gohr * 2323a56750bSAndreas Gohr * @param string[] $params 2333a56750bSAndreas Gohr * @param bool $form for use in form action? 2343a56750bSAndreas Gohr * @return string 2353a56750bSAndreas Gohr */ 2363a56750bSAndreas Gohr protected function selfLink($form = true, $params = []) 2373a56750bSAndreas Gohr { 2383a56750bSAndreas Gohr global $ID; 2393a56750bSAndreas Gohr $params = array_merge( 2403a56750bSAndreas Gohr [ 2413a56750bSAndreas Gohr 'do' => 'admin', 2423a56750bSAndreas Gohr 'page' => 'sqlite', 2433a56750bSAndreas Gohr 'db' => $this->db ? $this->db->getDBName() : '', 2443a56750bSAndreas Gohr 'sectok' => getSecurityToken(), 245a7a40fb2SAnna Dabrowska ], 246a7a40fb2SAnna Dabrowska $params 2473a56750bSAndreas Gohr ); 2483a56750bSAndreas Gohr 2493a56750bSAndreas Gohr return wl($ID, $params, false, $form ? '&' : '&'); 2503a56750bSAndreas Gohr } 2513a56750bSAndreas Gohr 2523a56750bSAndreas Gohr /** 2533a56750bSAndreas Gohr * Display the standard actions for a database 2543a56750bSAndreas Gohr */ 2553a56750bSAndreas Gohr protected function showCommands() 2563a56750bSAndreas Gohr { 2573a56750bSAndreas Gohr $commands = [ 2583a56750bSAndreas Gohr 'dbversion' => [ 2593a56750bSAndreas Gohr 'sql' => 'SELECT val FROM opts WHERE opt=\'dbversion\'', 2603a56750bSAndreas Gohr ], 2613a56750bSAndreas Gohr 'table' => [ 2623a56750bSAndreas Gohr 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'table\' ORDER BY name', 2633a56750bSAndreas Gohr ], 2643a56750bSAndreas Gohr 'index' => [ 2653a56750bSAndreas Gohr 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'index\' ORDER BY name', 2663a56750bSAndreas Gohr ], 2673a56750bSAndreas Gohr 'export' => [ 2683a56750bSAndreas Gohr 'cmd' => 'export' 2693a56750bSAndreas Gohr ], 27004a1c67aSAnna Dabrowska 'download' => [ 27104a1c67aSAnna Dabrowska 'cmd' => 'download' 27204a1c67aSAnna Dabrowska ], 2733a56750bSAndreas Gohr ]; 2743a56750bSAndreas Gohr 2753a56750bSAndreas Gohr // import form 2763a56750bSAndreas Gohr $form = new Form(['action' => $this->selfLink(), 'enctype' => 'multipart/form-data', 'method' => 'post']); 2779038f707SAndreas Gohr $form->addElement( 2789038f707SAndreas Gohr (new InputElement('file', 'importfile')) 2799038f707SAndreas Gohr ->attr('required', 'required') 2809038f707SAndreas Gohr ->attr('accept', '.sql') 2819038f707SAndreas Gohr ); 2823a56750bSAndreas Gohr $form->addButton('cmd[import]', $this->getLang('import')); 2833a56750bSAndreas Gohr 2843a56750bSAndreas Gohr // output as a list 2853a56750bSAndreas Gohr echo '<ul>'; 2863a56750bSAndreas Gohr foreach ($commands as $label => $command) { 2873a56750bSAndreas Gohr echo '<li><div class="li">'; 2883a56750bSAndreas Gohr echo '<a href="' . $this->selfLink(false, $command) . '">' . $this->getLang($label) . '</a>'; 2893a56750bSAndreas Gohr echo '</div></li>'; 2903a56750bSAndreas Gohr } 2913a56750bSAndreas Gohr echo '<li><div class="li">'; 2923a56750bSAndreas Gohr echo $form->toHTML(); 2933a56750bSAndreas Gohr echo '</div></li>'; 2943a56750bSAndreas Gohr echo '</ul>'; 2953a56750bSAndreas Gohr } 2963a56750bSAndreas Gohr 2973a56750bSAndreas Gohr /** 2989038f707SAndreas Gohr * Display the saved queries for this database 2993a56750bSAndreas Gohr */ 3003a56750bSAndreas Gohr public function showSavedQueries() 3013a56750bSAndreas Gohr { 3023a56750bSAndreas Gohr $queries = $this->querySaver->getQueries(); 3033a56750bSAndreas Gohr if (!$queries) return; 3043a56750bSAndreas Gohr 3053a56750bSAndreas Gohr echo '<ul>'; 3063a56750bSAndreas Gohr foreach ($queries as $query) { 3073a56750bSAndreas Gohr $link = $this->selfLink(false, ['sql' => $query['sql']]); 3083a56750bSAndreas Gohr $del = $this->selfLink(false, ['cmd' => 'delete_query', 'name' => $query['name']]); 3093a56750bSAndreas Gohr 3103a56750bSAndreas Gohr echo '<li><div class="li">'; 3113a56750bSAndreas Gohr echo '<a href="' . $link . '">' . hsc($query['name']) . '</a>'; 3123a56750bSAndreas Gohr echo ' [<a href="' . $del . '">' . $this->getLang('delete_query') . '</a>]'; 3133a56750bSAndreas Gohr echo '</div></li>'; 3143a56750bSAndreas Gohr } 3153a56750bSAndreas Gohr echo '</ul>'; 3163a56750bSAndreas Gohr } 3173a56750bSAndreas Gohr} 318