From 3a56750b42e05745f84fd97217f7133805e622eb Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Wed, 15 Mar 2023 22:03:56 +0100 Subject: [PATCH] admin interface cleanup Query saving has been moved to its own class --- QuerySaver.php | 65 ++++++ SQLiteDB.php | 7 +- admin.php | 524 +++++++++++++++++++++------------------------- db/update0001.sql | 11 +- helper.php | 4 +- lang/en/lang.php | 15 +- 6 files changed, 327 insertions(+), 299 deletions(-) create mode 100644 QuerySaver.php diff --git a/QuerySaver.php b/QuerySaver.php new file mode 100644 index 0000000..acb278f --- /dev/null +++ b/QuerySaver.php @@ -0,0 +1,65 @@ +db = new SQLiteDB('sqlite', DOKU_PLUGIN . 'sqlite/db/'); + $this->upstream = $dbname; + } + + /** + * Save a query + * + * @param string $name + * @param string $query + */ + public function saveQuery($name, $query) + { + $sql = 'INSERT INTO queries (db, name, sql) VALUES (?, ?, ?)'; + $this->db->exec($sql, [$this->upstream, $name, $query]); + } + + /** + * Get a saved query + * + * @param string $name + * @return string The SQL query + */ + public function getQuery($name) + { + $sql = 'SELECT sql FROM queries WHERE db = ? AND name = ?'; + return $this->db->queryValue($sql, [$this->upstream, $name]); + } + + /** + * Delete a saved query + * + * @param string $name + */ + public function deleteQuery($name) + { + $sql = 'DELETE FROM queries WHERE db = ? AND name = ?'; + $this->db->exec($sql, [$this->upstream, $name]); + } + + /** + * Get all saved queries + * + * @return array + */ + public function getQueries() + { + $sql = 'SELECT name, sql FROM queries WHERE db = ?'; + return $this->db->queryAll($sql, [$this->upstream]); + } +} diff --git a/SQLiteDB.php b/SQLiteDB.php index ba90725..34b378b 100644 --- a/SQLiteDB.php +++ b/SQLiteDB.php @@ -25,7 +25,7 @@ class SQLiteDB /** @var string */ protected $dbname; - /** @var \helper_plugin_sqlite + /** @var \helper_plugin_sqlite */ protected $helper; /** @@ -44,7 +44,10 @@ class SQLiteDB // backwards compatibility, circular dependency $this->helper = $sqlitehelper; - if($this->helper) $this->helper->setAdapter($this); + if(!$this->helper) { + $this->helper = new \helper_plugin_sqlite(); + } + $this->helper->setAdapter($this); $this->schemadir = $schemadir; $this->dbname = $dbname; diff --git a/admin.php b/admin.php index c6738d7..6291fc9 100644 --- a/admin.php +++ b/admin.php @@ -8,335 +8,293 @@ use dokuwiki\Form\Form; use dokuwiki\Form\InputElement; +use dokuwiki\plugin\sqlite\QuerySaver; +use dokuwiki\plugin\sqlite\SQLiteDB; +use dokuwiki\plugin\sqlite\Tools; -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); -class admin_plugin_sqlite extends DokuWiki_Admin_Plugin { +class admin_plugin_sqlite extends DokuWiki_Admin_Plugin +{ + /** @var SQLiteDB */ + protected $db = null; - function getMenuSort() { + /** @var QuerySaver */ + protected $querySaver = null; + + /** @inheritdoc */ + function getMenuSort() + { return 500; } - function forAdminOnly() { + /** @inheritdoc */ + function forAdminOnly() + { return true; } - function handle() { + /** @inheritdoc */ + function handle() + { global $conf; global $INPUT; - if($INPUT->bool('sqlite_rename') && checkSecurityToken()) { - - $path = $conf['metadir'].'/'.$INPUT->str('db'); - if(io_rename($path.'.sqlite', $path.'.sqlite3')) { - msg('Renamed database file succesfull!', 1); - //set to new situation - $INPUT->set('version', 'sqlite3'); - - } else { - msg('Renaming database file fails!', -1); + // load database if given and security token is valid + if ($INPUT->str('db') && checkSecurityToken()) { + try { + $this->db = new SQLiteDB($INPUT->str('db'), ''); + $this->querySaver = new QuerySaver($this->db->getDBName()); + } catch (Exception $e) { + msg($e->getMessage(), -1); + return; } - } elseif($INPUT->bool('sqlite_export') && checkSecurityToken()) { + } - /** @var $DBI helper_plugin_sqlite */ - $DBI = plugin_load('helper', 'sqlite'); - $dbname = $INPUT->str('db'); - - $dumpfile = $DBI->dumpDatabase($dbname, DOKU_EXT_PDO, true); - if ($dumpfile) { + $cmd = $INPUT->extract('cmd')->str('cmd'); + switch ($cmd) { + case 'export': + $exportfile = $conf['tmpdir'] . '/' . $this->db->getDBName() . '.sql'; + $this->db->dumpToFile($exportfile); header('Content-Type: text/sql'); - header('Content-Disposition: attachment; filename="'.$dbname.'.sql";'); - - readfile($dumpfile); + header('Content-Disposition: attachment; filename="' . $this->db->getDbName() . '.sql";'); + readfile($exportfile); + unlink($exportfile); exit(0); - } - } elseif($INPUT->bool('sqlite_import') && checkSecurityToken()) { + case 'import': + $importfile = $_FILES['importfile']['tmp_name']; - /** @var $DBI helper_plugin_sqlite */ - $DBI = plugin_load('helper', 'sqlite'); - $dbname = $INPUT->str('db'); - $dumpfile = $_FILES['dumpfile']['tmp_name']; + if (empty($importfile)) { + msg($this->getLang('import_no_file'), -1); + return; + } - if (empty($dumpfile)) { - msg($this->getLang('import_no_file'), -1); - return; - } - - if ($DBI->fillDatabaseFromDump($dbname, $dumpfile, true)) { - msg($this->getLang('import_success'), 1); - } - } elseif($INPUT->bool('sqlite_query_save') && checkSecurityToken()) { - if($INPUT->str('sql') === '') { - msg($this->getLang('validation query_required'), -1); - return; - } - if($INPUT->str('name') === '') { - msg($this->getLang('validation query_name_required'), -1); - return; - } - /** @var helper_plugin_sqlite_db $db_helper */ - $db_helper = $this->loadHelper('sqlite_db'); - $sqlite_db = $db_helper->getDB(); - $ok = $sqlite_db->storeEntry('queries', array( - 'db' => $INPUT->str('db'), - 'name' => $INPUT->str('name'), - 'sql' => $INPUT->str('sql') - )); - if ($ok) { - msg($this->getLang('success query_saved'), 1); - } - } elseif($INPUT->has('sqlite_query_delete') && checkSecurityToken()) { - /** @var helper_plugin_sqlite_db $db_helper */ - $db_helper = $this->loadHelper('sqlite_db'); - $sqlite_db = $db_helper->getDB(); - - $ok = $sqlite_db->query("DELETE FROM queries WHERE id=?;", $INPUT->int('sqlite_query_delete')); - if ($ok) { - msg($this->getLang('success query_deleted'), 1); - } + $sql = file_get_contents($importfile); + try { + $this->db->exec($sql); + msg($this->getLang('import_success'), 1); + } catch (Exception $e) { + msg($e->getMessage(), -1); + } + break; + case 'save_query': + $this->querySaver->saveQuery($INPUT->str('name'), $INPUT->str('sql')); + break; + case 'delete_query': + $this->querySaver->deleteQuery($INPUT->str('name')); + break; } } - function html() { - global $ID; - global $conf; + /** @inheritdoc */ + function html() + { global $INPUT; echo $this->locale_xhtml('intro'); - - if($INPUT->has('db') && checkSecurityToken()) { - - echo '

'.$this->getLang('db').' "'.hsc($INPUT->str('db')).'"

'; - echo '
'; - - $sqlcommandform = true; - /** @var $DBI helper_plugin_sqlite */ - $DBI = plugin_load('helper', 'sqlite'); - if($INPUT->str('version') == 'sqlite2') { - if(helper_plugin_sqlite_adapter::isSqlite3db($conf['metadir'].'/'.$INPUT->str('db').'.sqlite')) { - - msg('This is a database in sqlite3 format.', 2); - msg( - 'This plugin needs your database file has the extension ".sqlite3" - instead of ".sqlite" before it will be recognized as sqlite3 database.', 2 - ); - $action = wl($ID, [ - 'do' => 'admin', - 'page' => 'sqlite', - 'db' => $INPUT->str('db'), - 'version'=> $INPUT->str('version') - ], false, '&'); - $form = new Form(['action' => $action]); - $form->addButton('sqlite_rename', sprintf($this->getLang('rename2to3'), hsc($INPUT->str('db')))) - ->attr('type', 'submit'); - print $form->toHTML(); - - if($DBI->existsPDOSqlite()) $sqlcommandform = false; - - } else { - msg( - 'Before PDO sqlite can handle this format, it needs a conversion to the sqlite3 format. - Because PHP sqlite extension is no longer supported, - you should manually convert "'.hsc($INPUT->str('db')).'.sqlite" in the meta directory to "'.hsc($INPUT->str('db')).'.sqlite3".
- See for info about the conversion '.$this->external_link('http://www.sqlite.org/version3.html').'.', -1 - ); - $sqlcommandform = false; - } - } else { - if(!$DBI->existsPDOSqlite()) { - $sqlcommandform = false; - msg('A database in sqlite3 format needs the PHP PDO sqlite plugin.', -1); - } - } - - if($sqlcommandform) { - echo ''; + echo '

' . $this->getLang('db') . ' "' . hsc($this->db->getDbName()) . '"

'; + echo '
'; - $action = wl($ID, [ - 'do' => 'admin', - 'page' => 'sqlite', - 'db' => $INPUT->str('db'), - 'version'=> $INPUT->str('version') - ], false, '&'); - $form = (new Form(['action' => $action]))->addClass('sqliteplugin'); - $form->addFieldsetOpen('SQL Command'); - $form->addTextarea('sql')->addClass('edit')->val(hsc($INPUT->str('sql'))); - $form->addElement(new InputElement('submit', '')); - $form->addTextInput('name', $this->getLang('query_name')); - $form->addButton('sqlite_query_save', $this->getLang('save_query')); - $form->addFieldsetClose(); - print $form->toHTML(); + echo '
'; + $this->showCommands(); + $this->showSavedQueries(); + echo '
'; - // List saved queries - /** @var helper_plugin_sqlite_db $db_helper */ - $db_helper = $this->loadHelper('sqlite_db'); - $sqlite_db = $db_helper->getDB(); - $res = $sqlite_db->query("SELECT id, name, sql FROM queries WHERE db=?", $INPUT->str('db')); - $result = $sqlite_db->res2arr($res); - if(count($result) > 0) { - echo '

' . $this->getLang('saved_queries') . '

'; - echo '
'; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - foreach($result as $row) { - echo ''; - echo ''; - $link = wl($ID, array( 'do'=> 'admin', - 'page'=> 'sqlite', - 'db'=> $INPUT->str('db'), - 'version'=> $INPUT->str('version'), - 'sql' => $row['sql'], - 'sectok'=> getSecurityToken())); - echo ''; + // query form + $form = new Form(['action' => $this->selfLink()]); + $form->addClass('sqliteplugin'); + $form->addFieldsetOpen('SQL Command'); + $form->addTextarea('sql')->addClass('edit'); + $form->addButton('submit', $this->getLang('btn_execute'))->attr('type', 'submit'); + $form->addTextInput('name', $this->getLang('query_name')); + $form->addButton('cmd[save_query]', $this->getLang('save_query'))->attr('type', 'submit'); + $form->addFieldsetClose(); + echo $form->toHTML(); - $link = wl($ID, array( 'do'=> 'admin', - 'page'=> 'sqlite', - 'db'=> $INPUT->str('db'), - 'version'=> $INPUT->str('version'), - 'sqlite_query_delete' => $row['id'], - 'sectok'=> getSecurityToken())); - echo ''; - echo ''; - } - echo '
namesql
'.hsc($row['name']).''.hsc($row['sql']).'delete
'; - echo '
'; - } - - if($INPUT->has('sql')) { - if(!$DBI->init($INPUT->str('db'), '')) return; - - print '

Query results

'; - $sql = $DBI->SQLstring2array($INPUT->str('sql')); - foreach($sql as $s) { - $s = preg_replace('!^\s*--.*$!m', '', $s); - $s = trim($s); - if(!$s) continue; - - $time_start = microtime(true); - - $res = $DBI->query("$s;"); - if($res === false) continue; - - $result = $DBI->res2arr($res); - - $time_end = microtime(true); - $time = $time_end - $time_start; - - $cnt = $DBI->res2count($res); - msg($cnt.' affected rows in '.($time < 0.0001 ? substr($time, 0, 5).substr($time, -3) : substr($time, 0, 7)).' seconds', 1); - if(!$cnt) continue; - - echo '
'; - $ths = array_keys($result[0]); - echo ''; - echo ''; - foreach($ths as $th) { - echo ''; - } - echo ''; - foreach($result as $row) { - echo ''; - $tds = array_values($row); - foreach($tds as $td) { - if($td === null) $td='␀'; - echo ''; - } - echo ''; - } - echo '
'.hsc($th).'
'.hsc($td).'
'; - echo '
'; - } - } - - } - echo '
'; - } + // results + if ($INPUT->has('sql')) $this->showQueryResults($INPUT->str('sql')); + echo '
'; } - function getTOC() { + function getTOC() + { global $conf; global $ID; - $toc = array(); - $fileextensions = array('sqlite2'=> '.sqlite', 'sqlite3'=> '.sqlite3'); + $toc = array(); + $fileextensions = array('sqlite2' => '.sqlite', 'sqlite3' => '.sqlite3'); - foreach($fileextensions as $dbformat => $fileextension) { + foreach ($fileextensions as $dbformat => $fileextension) { $toc[] = array( - 'link' => wl($ID, array('do'=> 'admin', 'page'=> 'sqlite')), - 'title' => $dbformat.':', + 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite')), + 'title' => $dbformat . ':', 'level' => 1, - 'type' => 'ul', + 'type' => 'ul', ); - $dbfiles = glob($conf['metadir'].'/*'.$fileextension); + $dbfiles = glob($conf['metadir'] . '/*' . $fileextension); - if(is_array($dbfiles)) foreach($dbfiles as $file) { - $db = basename($file, $fileextension); + if (is_array($dbfiles)) foreach ($dbfiles as $file) { + $db = basename($file, $fileextension); $toc[] = array( - 'link' => wl($ID, array('do'=> 'admin', 'page'=> 'sqlite', 'db'=> $db, 'version'=> $dbformat, 'sectok'=> getSecurityToken())), - 'title' => $this->getLang('db').' '.$db, + 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite', 'db' => $db, 'version' => $dbformat, 'sectok' => getSecurityToken())), + 'title' => $this->getLang('db') . ' ' . $db, 'level' => 2, - 'type' => 'ul', + 'type' => 'ul', ); } } return $toc; } -} -// vim:ts=4:sw=4:et: + /** + * Execute and display the results of the given SQL query + * + * multiple queries can be given separated by semicolons + * + * @param string $sql + */ + protected function showQueryResults($sql) + { + echo '

Query results

'; + + $sql = Tools::SQLstring2array($sql); + foreach ($sql as $s) { + $s = preg_replace('!^\s*--.*$!m', '', $s); + $s = trim($s); + if (!$s) continue; + + try { + $time_start = microtime(true); + $result = $this->db->queryAll($s); + $time_end = microtime(true); + } catch (Exception $e) { + msg($e->getMessage(), -1); + continue; + } + + $time = $time_end - $time_start; + $cnt = count($result); + msg($cnt . ' affected rows in ' . $this->microtimeToSeconds($time) . ' seconds', 1); + if (!$cnt) continue; + + echo '
'; + $ths = array_keys($result[0]); + echo ''; + echo ''; + foreach ($ths as $th) { + echo ''; + } + echo ''; + foreach ($result as $row) { + echo ''; + $tds = array_values($row); + foreach ($tds as $td) { + if ($td === null) $td = '␀'; + echo ''; + } + echo ''; + } + echo '
' . hsc($th) . '
' . hsc($td) . '
'; + echo '
'; + } + } + + + /** + * Convert a microtime() value to a string in seconds + * + * @param float $time + * @return string + */ + protected function microtimeToSeconds($time) + { + return ($time < 0.0001 ? substr($time, 0, 5) . substr($time, -3) : substr($time, 0, 7)); + } + + /** + * Construct a link to the sqlite admin page with the given additional parameters + * + * Basically a wrapper around wl() with some defaults + * + * @param string[] $params + * @param bool $form for use in form action? + * @return string + */ + protected function selfLink($form = true, $params = []) + { + global $ID; + $params = array_merge( + [ + 'do' => 'admin', + 'page' => 'sqlite', + 'db' => $this->db ? $this->db->getDBName() : '', + 'sectok' => getSecurityToken(), + ], $params + ); + + return wl($ID, $params, false, $form ? '&' : '&'); + } + + /** + * Display the standard actions for a database + */ + protected function showCommands() + { + $commands = [ + 'dbversion' => [ + 'sql' => 'SELECT val FROM opts WHERE opt=\'dbversion\'', + ], + 'table' => [ + 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'table\' ORDER BY name', + ], + 'index' => [ + 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'index\' ORDER BY name', + ], + 'export' => [ + 'cmd' => 'export' + ], + ]; + + // import form + $form = new Form(['action' => $this->selfLink(), 'enctype' => 'multipart/form-data', 'method' => 'post']); + $form->addElement(new InputElement('file', 'importfile')); + $form->addButton('cmd[import]', $this->getLang('import')); + + // output as a list + echo ''; + } + + /** + * FIXME needs to be cleaned up + */ + public function showSavedQueries() + { + $queries = $this->querySaver->getQueries(); + if (!$queries) return; + + echo ''; + } +} diff --git a/db/update0001.sql b/db/update0001.sql index e23d8f9..82bb981 100644 --- a/db/update0001.sql +++ b/db/update0001.sql @@ -1,7 +1,8 @@ -- Defines the queries that can be used in wiki -CREATE TABLE queries ( - id INTEGER PRIMARY KEY, - db TEXT NOT NULL, +CREATE TABLE queries +( + db TEXT NOT NULL, name TEXT NOT NULL, - sql TEXT NOT NULL -); \ No newline at end of file + sql TEXT NOT NULL, + PRIMARY KEY (db, name) +); diff --git a/helper.php b/helper.php index 07fb6a3..d186fb8 100755 --- a/helper.php +++ b/helper.php @@ -30,8 +30,6 @@ class helper_plugin_sqlite extends DokuWiki_Plugin */ public function __construct() { - dbg_deprecated(SQLiteDB::class); - if (!$this->existsPDOSqlite()) { msg('PDO SQLite support missing in this PHP install - The sqlite plugin will not work', -1); } @@ -76,6 +74,8 @@ class helper_plugin_sqlite extends DokuWiki_Plugin */ public function init($dbname, $updatedir) { + dbg_deprecated(SQLiteDB::class); + try { $this->adapter = new SQLiteDB($dbname, $updatedir, $this); } catch (Exception $e) { diff --git a/lang/en/lang.php b/lang/en/lang.php index a933442..d7a4ebb 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -3,22 +3,23 @@ $lang['menu'] = 'SQLite Interface'; $lang['db'] = 'Database'; -$lang['index'] = 'list indexes'; -$lang['table'] = 'list tables'; +$lang['dbversion'] = 'Show Database Version'; +$lang['index'] = 'List Indexes'; +$lang['table'] = 'List Tables'; -$lang['rename2to3'] = 'Rename %s.sqlite to *.sqlite3'; -$lang['convert2to3'] = 'Convert %s from Sqlite2 to Sqlite3 format'; +$lang['btn_execute'] = 'Execute Query'; -$lang['export'] = 'export database'; -$lang['import'] = 'import database'; +$lang['export'] = 'Export Database'; +$lang['import'] = 'Import Database'; $lang['import_no_file'] = 'Select file'; $lang['import_success'] = 'Database imported successfully'; $lang['none'] = 'Nothing found'; $lang['query_name'] = 'Query name'; $lang['save_query'] = 'Save query'; +$lang['delete_query'] = 'Delete'; $lang['saved_queries'] = 'Saved queries'; $lang['validation query_required'] = 'SQL command is required.'; $lang['validation query_name_required'] = 'Query name is required.'; $lang['success query_saved'] = 'Query saved.'; -$lang['success query_deleted'] = 'Query deleted.'; \ No newline at end of file +$lang['success query_deleted'] = 'Query deleted.';