xref: /plugin/sqlite/admin.php (revision 04a1c67a0ed8236614f88389948e28711a484340)
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 ? '&' : '&amp;');
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