xref: /plugin/sqlite/admin.php (revision a7a40fb292e55f99ed1ae34981feed1314e2696b)
193d995e0SAndreas Gohr<?php
293d995e0SAndreas Gohr
3*a7a40fb2SAnna 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 */
16*a7a40fb2SAnna Dabrowskaclass admin_plugin_sqlite extends AdminPlugin
173a56750bSAndreas Gohr{
183a56750bSAndreas Gohr    /** @var SQLiteDB */
19*a7a40fb2SAnna Dabrowska    protected $db;
2093d995e0SAndreas Gohr
213a56750bSAndreas Gohr    /** @var QuerySaver */
22*a7a40fb2SAnna 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');
9304a1c67aSAnna Dabrowska                header('Content-Disposition: attachment; filename="' . $this->db->getDbName() . SQLiteDB::FILE_EXTENSION . '"');
9404a1c67aSAnna Dabrowska                readfile($file);
9504a1c67aSAnna Dabrowska                exit(0);
96a34ef333SKlap-in        }
9793d995e0SAndreas Gohr    }
9893d995e0SAndreas Gohr
993a56750bSAndreas Gohr    /** @inheritdoc */
1009038f707SAndreas Gohr    public function html()
1013a56750bSAndreas Gohr    {
102271f9caeSSzymon Olewniczak        global $INPUT;
10393d995e0SAndreas Gohr
1049038f707SAndreas Gohr        echo '<div class="plugin_sqlite_admin">';
10593d995e0SAndreas Gohr        echo $this->locale_xhtml('intro');
10693d995e0SAndreas Gohr
1079038f707SAndreas Gohr        if ($this->db) {
1083a56750bSAndreas Gohr            echo '<h2>' . $this->getLang('db') . ' "' . hsc($this->db->getDbName()) . '"</h2>';
10993d995e0SAndreas Gohr
1103a56750bSAndreas Gohr            echo '<div class="commands">';
1113a56750bSAndreas Gohr            $this->showCommands();
1123a56750bSAndreas Gohr            $this->showSavedQueries();
1133a56750bSAndreas Gohr            echo '</div>';
114a34ef333SKlap-in
1153a56750bSAndreas Gohr            // query form
1163a56750bSAndreas Gohr            $form = new Form(['action' => $this->selfLink()]);
1173a56750bSAndreas Gohr            $form->addClass('sqliteplugin');
118ecc45fdcSSzymon Olewniczak            $form->addFieldsetOpen('SQL Command');
1193a56750bSAndreas Gohr            $form->addTextarea('sql')->addClass('edit');
1203a56750bSAndreas Gohr            $form->addButton('submit', $this->getLang('btn_execute'))->attr('type', 'submit');
121ecc45fdcSSzymon Olewniczak            $form->addTextInput('name', $this->getLang('query_name'));
1223a56750bSAndreas Gohr            $form->addButton('cmd[save_query]', $this->getLang('save_query'))->attr('type', 'submit');
123ecc45fdcSSzymon Olewniczak            $form->addFieldsetClose();
1243a56750bSAndreas Gohr            echo $form->toHTML();
12593d995e0SAndreas Gohr
1263a56750bSAndreas Gohr            // results
1273a56750bSAndreas Gohr            if ($INPUT->has('sql')) $this->showQueryResults($INPUT->str('sql'));
1289038f707SAndreas Gohr        }
129271f9caeSSzymon Olewniczak        echo '</div>';
130271f9caeSSzymon Olewniczak    }
131271f9caeSSzymon Olewniczak
1325a212f8eSAndreas Gohr    /**
1335a212f8eSAndreas Gohr     * List all available databases in the TOC
1345a212f8eSAndreas Gohr     *
1355a212f8eSAndreas Gohr     * @inheritdoc
1365a212f8eSAndreas Gohr     */
1375a212f8eSAndreas Gohr    public function getTOC()
1383a56750bSAndreas Gohr    {
13993d995e0SAndreas Gohr        global $conf;
14093d995e0SAndreas Gohr        global $ID;
14193d995e0SAndreas Gohr
1425a212f8eSAndreas Gohr        $toc = [];
1435a212f8eSAndreas Gohr        $toc[] = [
1445a212f8eSAndreas Gohr            'link' => wl($ID, ['do' => 'admin', 'page' => 'sqlite']),
1455a212f8eSAndreas Gohr            'title' => $this->getLang('db') . ':',
14693d995e0SAndreas Gohr            'level' => 1,
14793d995e0SAndreas Gohr            'type' => 'ul',
1485a212f8eSAndreas Gohr        ];
1495a212f8eSAndreas Gohr        $dbfiles = glob($conf['metadir'] . '/*.sqlite3');
15040698f67SKlap-in        if (is_array($dbfiles)) foreach ($dbfiles as $file) {
1515a212f8eSAndreas Gohr            $db = basename($file, '.sqlite3');
152*a7a40fb2SAnna Dabrowska            $toc[] = ['link' => wl($ID, ['do' => 'admin', 'page' => 'sqlite', 'db' => $db, 'sectok' => getSecurityToken()]), 'title' => $db, 'level' => 2, 'type' => 'ul'];
15340698f67SKlap-in        }
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            try {
1723a56750bSAndreas Gohr                $time_start = microtime(true);
1733a56750bSAndreas Gohr                $result = $this->db->queryAll($s);
1743a56750bSAndreas Gohr                $time_end = microtime(true);
1753a56750bSAndreas Gohr            } catch (Exception $e) {
1763a56750bSAndreas Gohr                msg($e->getMessage(), -1);
1773a56750bSAndreas Gohr                continue;
17893d995e0SAndreas Gohr            }
17993d995e0SAndreas Gohr
1803a56750bSAndreas Gohr            $time = $time_end - $time_start;
1813a56750bSAndreas Gohr            $cnt = count($result);
1823a56750bSAndreas Gohr            msg($cnt . ' affected rows in ' . $this->microtimeToSeconds($time) . ' seconds', 1);
1833a56750bSAndreas Gohr            if (!$cnt) continue;
1843a56750bSAndreas Gohr
1853a56750bSAndreas Gohr            echo '<div>';
1863a56750bSAndreas Gohr            $ths = array_keys($result[0]);
1873a56750bSAndreas Gohr            echo '<table class="inline">';
1883a56750bSAndreas Gohr            echo '<tr>';
1893a56750bSAndreas Gohr            foreach ($ths as $th) {
1903a56750bSAndreas Gohr                echo '<th>' . hsc($th) . '</th>';
1913a56750bSAndreas Gohr            }
1923a56750bSAndreas Gohr            echo '</tr>';
1933a56750bSAndreas Gohr            foreach ($result as $row) {
1943a56750bSAndreas Gohr                echo '<tr>';
1953a56750bSAndreas Gohr                $tds = array_values($row);
1963a56750bSAndreas Gohr                foreach ($tds as $td) {
1973a56750bSAndreas Gohr                    if ($td === null) $td = '␀';
1983a56750bSAndreas Gohr                    echo '<td>' . hsc($td) . '</td>';
1993a56750bSAndreas Gohr                }
2003a56750bSAndreas Gohr                echo '</tr>';
2013a56750bSAndreas Gohr            }
2023a56750bSAndreas Gohr            echo '</table>';
2033a56750bSAndreas Gohr            echo '</div>';
2043a56750bSAndreas Gohr        }
2053a56750bSAndreas Gohr    }
2063a56750bSAndreas Gohr
2073a56750bSAndreas Gohr
2083a56750bSAndreas Gohr    /**
2093a56750bSAndreas Gohr     * Convert a microtime() value to a string in seconds
2103a56750bSAndreas Gohr     *
2113a56750bSAndreas Gohr     * @param float $time
2123a56750bSAndreas Gohr     * @return string
2133a56750bSAndreas Gohr     */
2143a56750bSAndreas Gohr    protected function microtimeToSeconds($time)
2153a56750bSAndreas Gohr    {
2163a56750bSAndreas Gohr        return ($time < 0.0001 ? substr($time, 0, 5) . substr($time, -3) : substr($time, 0, 7));
2173a56750bSAndreas Gohr    }
2183a56750bSAndreas Gohr
2193a56750bSAndreas Gohr    /**
2203a56750bSAndreas Gohr     * Construct a link to the sqlite admin page with the given additional parameters
2213a56750bSAndreas Gohr     *
2223a56750bSAndreas Gohr     * Basically a wrapper around wl() with some defaults
2233a56750bSAndreas Gohr     *
2243a56750bSAndreas Gohr     * @param string[] $params
2253a56750bSAndreas Gohr     * @param bool $form for use in form action?
2263a56750bSAndreas Gohr     * @return string
2273a56750bSAndreas Gohr     */
2283a56750bSAndreas Gohr    protected function selfLink($form = true, $params = [])
2293a56750bSAndreas Gohr    {
2303a56750bSAndreas Gohr        global $ID;
2313a56750bSAndreas Gohr        $params = array_merge(
2323a56750bSAndreas Gohr            [
2333a56750bSAndreas Gohr                'do' => 'admin',
2343a56750bSAndreas Gohr                'page' => 'sqlite',
2353a56750bSAndreas Gohr                'db' => $this->db ? $this->db->getDBName() : '',
2363a56750bSAndreas Gohr                'sectok' => getSecurityToken(),
237*a7a40fb2SAnna Dabrowska            ],
238*a7a40fb2SAnna Dabrowska            $params
2393a56750bSAndreas Gohr        );
2403a56750bSAndreas Gohr
2413a56750bSAndreas Gohr        return wl($ID, $params, false, $form ? '&' : '&amp;');
2423a56750bSAndreas Gohr    }
2433a56750bSAndreas Gohr
2443a56750bSAndreas Gohr    /**
2453a56750bSAndreas Gohr     * Display the standard actions for a database
2463a56750bSAndreas Gohr     */
2473a56750bSAndreas Gohr    protected function showCommands()
2483a56750bSAndreas Gohr    {
2493a56750bSAndreas Gohr        $commands = [
2503a56750bSAndreas Gohr            'dbversion' => [
2513a56750bSAndreas Gohr                'sql' => 'SELECT val FROM opts WHERE opt=\'dbversion\'',
2523a56750bSAndreas Gohr            ],
2533a56750bSAndreas Gohr            'table' => [
2543a56750bSAndreas Gohr                'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'table\' ORDER BY name',
2553a56750bSAndreas Gohr            ],
2563a56750bSAndreas Gohr            'index' => [
2573a56750bSAndreas Gohr                'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'index\' ORDER BY name',
2583a56750bSAndreas Gohr            ],
2593a56750bSAndreas Gohr            'export' => [
2603a56750bSAndreas Gohr                'cmd' => 'export'
2613a56750bSAndreas Gohr            ],
26204a1c67aSAnna Dabrowska            'download' => [
26304a1c67aSAnna Dabrowska                'cmd' => 'download'
26404a1c67aSAnna Dabrowska            ],
2653a56750bSAndreas Gohr        ];
2663a56750bSAndreas Gohr
2673a56750bSAndreas Gohr        // import form
2683a56750bSAndreas Gohr        $form = new Form(['action' => $this->selfLink(), 'enctype' => 'multipart/form-data', 'method' => 'post']);
2699038f707SAndreas Gohr        $form->addElement(
2709038f707SAndreas Gohr            (new InputElement('file', 'importfile'))
2719038f707SAndreas Gohr                ->attr('required', 'required')
2729038f707SAndreas Gohr                ->attr('accept', '.sql')
2739038f707SAndreas Gohr        );
2743a56750bSAndreas Gohr        $form->addButton('cmd[import]', $this->getLang('import'));
2753a56750bSAndreas Gohr
2763a56750bSAndreas Gohr        // output as a list
2773a56750bSAndreas Gohr        echo '<ul>';
2783a56750bSAndreas Gohr        foreach ($commands as $label => $command) {
2793a56750bSAndreas Gohr            echo '<li><div class="li">';
2803a56750bSAndreas Gohr            echo '<a href="' . $this->selfLink(false, $command) . '">' . $this->getLang($label) . '</a>';
2813a56750bSAndreas Gohr            echo '</div></li>';
2823a56750bSAndreas Gohr        }
2833a56750bSAndreas Gohr        echo '<li><div class="li">';
2843a56750bSAndreas Gohr        echo $form->toHTML();
2853a56750bSAndreas Gohr        echo '</div></li>';
2863a56750bSAndreas Gohr        echo '</ul>';
2873a56750bSAndreas Gohr    }
2883a56750bSAndreas Gohr
2893a56750bSAndreas Gohr    /**
2909038f707SAndreas Gohr     * Display the saved queries for this database
2913a56750bSAndreas Gohr     */
2923a56750bSAndreas Gohr    public function showSavedQueries()
2933a56750bSAndreas Gohr    {
2943a56750bSAndreas Gohr        $queries = $this->querySaver->getQueries();
2953a56750bSAndreas Gohr        if (!$queries) return;
2963a56750bSAndreas Gohr
2973a56750bSAndreas Gohr        echo '<ul>';
2983a56750bSAndreas Gohr        foreach ($queries as $query) {
2993a56750bSAndreas Gohr            $link = $this->selfLink(false, ['sql' => $query['sql']]);
3003a56750bSAndreas Gohr            $del = $this->selfLink(false, ['cmd' => 'delete_query', 'name' => $query['name']]);
3013a56750bSAndreas Gohr
3023a56750bSAndreas Gohr            echo '<li><div class="li">';
3033a56750bSAndreas Gohr            echo '<a href="' . $link . '">' . hsc($query['name']) . '</a>';
3043a56750bSAndreas Gohr            echo ' [<a href="' . $del . '">' . $this->getLang('delete_query') . '</a>]';
3053a56750bSAndreas Gohr            echo '</div></li>';
3063a56750bSAndreas Gohr        }
3073a56750bSAndreas Gohr        echo '</ul>';
3083a56750bSAndreas Gohr    }
3093a56750bSAndreas Gohr}
310