1<?php 2/** 3 * DokuWiki Plugin sqlite (Admin Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9use dokuwiki\Form\Form; 10use dokuwiki\Form\InputElement; 11use dokuwiki\plugin\sqlite\QuerySaver; 12use dokuwiki\plugin\sqlite\SQLiteDB; 13use dokuwiki\plugin\sqlite\Tools; 14 15 16class admin_plugin_sqlite extends DokuWiki_Admin_Plugin 17{ 18 /** @var SQLiteDB */ 19 protected $db = null; 20 21 /** @var QuerySaver */ 22 protected $querySaver = null; 23 24 /** @inheritdoc */ 25 function getMenuSort() 26 { 27 return 500; 28 } 29 30 /** @inheritdoc */ 31 function forAdminOnly() 32 { 33 return true; 34 } 35 36 /** @inheritdoc */ 37 function handle() 38 { 39 global $conf; 40 global $INPUT; 41 42 // load database if given and security token is valid 43 if ($INPUT->str('db') && checkSecurityToken()) { 44 try { 45 $this->db = new SQLiteDB($INPUT->str('db'), ''); 46 $this->querySaver = new QuerySaver($this->db->getDBName()); 47 } catch (Exception $e) { 48 msg($e->getMessage(), -1); 49 return; 50 } 51 } 52 53 $cmd = $INPUT->extract('cmd')->str('cmd'); 54 switch ($cmd) { 55 case 'export': 56 $exportfile = $conf['tmpdir'] . '/' . $this->db->getDBName() . '.sql'; 57 $this->db->dumpToFile($exportfile); 58 header('Content-Type: text/sql'); 59 header('Content-Disposition: attachment; filename="' . $this->db->getDbName() . '.sql";'); 60 readfile($exportfile); 61 unlink($exportfile); 62 exit(0); 63 case 'import': 64 $importfile = $_FILES['importfile']['tmp_name']; 65 66 if (empty($importfile)) { 67 msg($this->getLang('import_no_file'), -1); 68 return; 69 } 70 71 $sql = file_get_contents($importfile); 72 try { 73 $this->db->exec($sql); 74 msg($this->getLang('import_success'), 1); 75 } catch (Exception $e) { 76 msg($e->getMessage(), -1); 77 } 78 break; 79 case 'save_query': 80 $this->querySaver->saveQuery($INPUT->str('name'), $INPUT->str('sql')); 81 break; 82 case 'delete_query': 83 $this->querySaver->deleteQuery($INPUT->str('name')); 84 break; 85 } 86 } 87 88 /** @inheritdoc */ 89 function html() 90 { 91 global $INPUT; 92 93 echo $this->locale_xhtml('intro'); 94 if (!$this->db) return; 95 96 97 echo '<h2>' . $this->getLang('db') . ' "' . hsc($this->db->getDbName()) . '"</h2>'; 98 echo '<div class="level2">'; 99 100 echo '<div class="commands">'; 101 $this->showCommands(); 102 $this->showSavedQueries(); 103 echo '</div>'; 104 105 // query form 106 $form = new Form(['action' => $this->selfLink()]); 107 $form->addClass('sqliteplugin'); 108 $form->addFieldsetOpen('SQL Command'); 109 $form->addTextarea('sql')->addClass('edit'); 110 $form->addButton('submit', $this->getLang('btn_execute'))->attr('type', 'submit'); 111 $form->addTextInput('name', $this->getLang('query_name')); 112 $form->addButton('cmd[save_query]', $this->getLang('save_query'))->attr('type', 'submit'); 113 $form->addFieldsetClose(); 114 echo $form->toHTML(); 115 116 // results 117 if ($INPUT->has('sql')) $this->showQueryResults($INPUT->str('sql')); 118 echo '</div>'; 119 } 120 121 function getTOC() 122 { 123 global $conf; 124 global $ID; 125 126 $toc = array(); 127 $fileextensions = array('sqlite2' => '.sqlite', 'sqlite3' => '.sqlite3'); 128 129 foreach ($fileextensions as $dbformat => $fileextension) { 130 $toc[] = array( 131 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite')), 132 'title' => $dbformat . ':', 133 'level' => 1, 134 'type' => 'ul', 135 ); 136 137 $dbfiles = glob($conf['metadir'] . '/*' . $fileextension); 138 139 if (is_array($dbfiles)) foreach ($dbfiles as $file) { 140 $db = basename($file, $fileextension); 141 $toc[] = array( 142 'link' => wl($ID, array('do' => 'admin', 'page' => 'sqlite', 'db' => $db, 'version' => $dbformat, 'sectok' => getSecurityToken())), 143 'title' => $this->getLang('db') . ' ' . $db, 144 'level' => 2, 145 'type' => 'ul', 146 ); 147 } 148 } 149 150 return $toc; 151 } 152 153 /** 154 * Execute and display the results of the given SQL query 155 * 156 * multiple queries can be given separated by semicolons 157 * 158 * @param string $sql 159 */ 160 protected function showQueryResults($sql) 161 { 162 echo '<h3 id="scroll__here">Query results</h3>'; 163 164 $sql = Tools::SQLstring2array($sql); 165 foreach ($sql as $s) { 166 $s = preg_replace('!^\s*--.*$!m', '', $s); 167 $s = trim($s); 168 if (!$s) continue; 169 170 try { 171 $time_start = microtime(true); 172 $result = $this->db->queryAll($s); 173 $time_end = microtime(true); 174 } catch (Exception $e) { 175 msg($e->getMessage(), -1); 176 continue; 177 } 178 179 $time = $time_end - $time_start; 180 $cnt = count($result); 181 msg($cnt . ' affected rows in ' . $this->microtimeToSeconds($time) . ' seconds', 1); 182 if (!$cnt) continue; 183 184 echo '<div>'; 185 $ths = array_keys($result[0]); 186 echo '<table class="inline">'; 187 echo '<tr>'; 188 foreach ($ths as $th) { 189 echo '<th>' . hsc($th) . '</th>'; 190 } 191 echo '</tr>'; 192 foreach ($result as $row) { 193 echo '<tr>'; 194 $tds = array_values($row); 195 foreach ($tds as $td) { 196 if ($td === null) $td = '␀'; 197 echo '<td>' . hsc($td) . '</td>'; 198 } 199 echo '</tr>'; 200 } 201 echo '</table>'; 202 echo '</div>'; 203 } 204 } 205 206 207 /** 208 * Convert a microtime() value to a string in seconds 209 * 210 * @param float $time 211 * @return string 212 */ 213 protected function microtimeToSeconds($time) 214 { 215 return ($time < 0.0001 ? substr($time, 0, 5) . substr($time, -3) : substr($time, 0, 7)); 216 } 217 218 /** 219 * Construct a link to the sqlite admin page with the given additional parameters 220 * 221 * Basically a wrapper around wl() with some defaults 222 * 223 * @param string[] $params 224 * @param bool $form for use in form action? 225 * @return string 226 */ 227 protected function selfLink($form = true, $params = []) 228 { 229 global $ID; 230 $params = array_merge( 231 [ 232 'do' => 'admin', 233 'page' => 'sqlite', 234 'db' => $this->db ? $this->db->getDBName() : '', 235 'sectok' => getSecurityToken(), 236 ], $params 237 ); 238 239 return wl($ID, $params, false, $form ? '&' : '&'); 240 } 241 242 /** 243 * Display the standard actions for a database 244 */ 245 protected function showCommands() 246 { 247 $commands = [ 248 'dbversion' => [ 249 'sql' => 'SELECT val FROM opts WHERE opt=\'dbversion\'', 250 ], 251 'table' => [ 252 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'table\' ORDER BY name', 253 ], 254 'index' => [ 255 'sql' => 'SELECT name,sql FROM sqlite_master WHERE type=\'index\' ORDER BY name', 256 ], 257 'export' => [ 258 'cmd' => 'export' 259 ], 260 ]; 261 262 // import form 263 $form = new Form(['action' => $this->selfLink(), 'enctype' => 'multipart/form-data', 'method' => 'post']); 264 $form->addElement(new InputElement('file', 'importfile')); 265 $form->addButton('cmd[import]', $this->getLang('import')); 266 267 // output as a list 268 echo '<ul>'; 269 foreach ($commands as $label => $command) { 270 echo '<li><div class="li">'; 271 echo '<a href="' . $this->selfLink(false, $command) . '">' . $this->getLang($label) . '</a>'; 272 echo '</div></li>'; 273 } 274 echo '<li><div class="li">'; 275 echo $form->toHTML(); 276 echo '</div></li>'; 277 echo '</ul>'; 278 } 279 280 /** 281 * FIXME needs to be cleaned up 282 */ 283 public function showSavedQueries() 284 { 285 $queries = $this->querySaver->getQueries(); 286 if (!$queries) return; 287 288 echo '<ul>'; 289 foreach ($queries as $query) { 290 $link = $this->selfLink(false, ['sql' => $query['sql']]); 291 $del = $this->selfLink(false, ['cmd' => 'delete_query', 'name' => $query['name']]); 292 293 echo '<li><div class="li">'; 294 echo '<a href="' . $link . '">' . hsc($query['name']) . '</a>'; 295 echo ' [<a href="' . $del . '">' . $this->getLang('delete_query') . '</a>]'; 296 echo '</div></li>'; 297 } 298 echo '</ul>'; 299 } 300} 301