1<?php 2 3/** 4 * Dokuwiki Advanced Config Plugin 5 * 6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com> 8 */ 9 10class admin_plugin_advanced_config extends DokuWiki_Admin_Plugin 11{ 12 13 private $allowedFiles = array(); 14 private $fileInfo = array(); 15 16 /** 17 * @return int sort number in admin menu 18 */ 19 public function getMenuSort() 20 { 21 return 1; 22 } 23 24 /** 25 * @return bool true if only access for superuser, false is for superusers and moderators 26 */ 27 public function forAdminOnly() 28 { 29 return true; 30 } 31 32 public function getMenuIcon() 33 { 34 return dirname(__FILE__) . '/../svg/cogs.svg'; 35 } 36 37 public function getMenuText($language) 38 { 39 return $this->getLang('menu_config'); 40 } 41 42 /** 43 * handle user request 44 */ 45 public function handle() 46 { 47 48 global $INPUT; 49 50 if (!isset($_REQUEST['cmd'])) { 51 return; 52 } 53 54 if (!checkSecurityToken()) { 55 return; 56 } 57 58 $cmd = $INPUT->extract('cmd')->str('cmd'); 59 60 if ($cmd) { 61 $cmd = "cmd_$cmd"; 62 $this->$cmd(); 63 } 64 65 } 66 67 /** 68 * Get configuration file info 69 * 70 * @return array 71 */ 72 private function getFileInfo() 73 { 74 75 global $INPUT; 76 global $conf; 77 global $config_cascade; 78 79 $file = $INPUT->str('file'); 80 $tab = $INPUT->str('tab'); 81 82 $file_local = null; 83 $file_default = null; 84 $file_protected = null; 85 86 if (!$file || !$tab) { 87 return array(); 88 } 89 90 switch ($tab) { 91 92 case 'config': 93 94 $configs = $config_cascade[$file]; 95 96 $file_default = @$configs['default'][0]; 97 $file_local = @$configs['local'][0]; 98 $file_protected = @$configs['protected'][0]; 99 break; 100 101 case 'userstyle': 102 case 'userscript': 103 104 $configs = $config_cascade[$tab][$file]; 105 106 # Detect new DokuWiki release config (css, less) 107 if (is_array(@$configs)) { 108 $file_local = @$configs[0]; 109 } else { 110 $file_local = $configs; 111 } 112 113 break; 114 115 case 'hook': 116 $file_local = DOKU_CONF . "$file.html"; 117 $file_default = tpl_incdir() . "$file.html"; 118 break; 119 120 case 'plugin': 121 $file_local = DOKU_CONF . $file; 122 break; 123 124 } 125 126 switch ($file) { 127 128 case 'styleini': 129 $file_local = str_replace('%TEMPLATE%', $conf['template'], $file_local); 130 $file_default = str_replace('%TEMPLATE%', $conf['template'], $file_default); 131 break; 132 case 'acl': 133 $file_local = DOKU_CONF . 'acl.auth.php'; 134 $file_default = DOKU_CONF . 'acl.auth.php.dist'; 135 break; 136 137 case 'users': 138 $file_local = DOKU_CONF . 'users.auth.php'; 139 $file_default = DOKU_CONF . 'users.auth.php.dist'; 140 break; 141 142 case 'htaccess': 143 $file_default = DOKU_INC . '.htaccess.dist'; 144 $file_local = DOKU_INC . '.htaccess'; 145 break; 146 147 case 'userscript': 148 $configs = $config_cascade['userscript']; 149 if (is_array(@$configs['default'])) { 150 $file_local = @$configs['default'][0]; 151 } else { 152 $file_local = @$configs['default']; 153 } 154 $file_default = null; 155 break; 156 157 } 158 159 $file_info = array( 160 'tab' => $tab, 161 'file' => $file, 162 'default' => $file_default, 163 'local' => $file_local, 164 'protected' => $file_protected, 165 'local_name' => basename($file_local), 166 'default_name' => basename($file_default), 167 'protected_name' => basename($file_protected), 168 'local_last_modify' => (file_exists($file_local) ? dformat(filemtime($file_local)) : ''), 169 'protected_last_modify' => (file_exists($file_protected) ? dformat(filemtime($file_protected)) : ''), 170 'default_last_modify' => (file_exists($file_default) ? dformat(filemtime($file_default)) : ''), 171 'help' => 'config/' . $file, 172 ); 173 174 return $file_info; 175 176 } 177 178 public function cmd_save() 179 { 180 181 global $INPUT; 182 183 $file_info = $this->getFileInfo(); 184 185 $file_path = $file_info['local']; 186 $file_name = $file_info['localName']; 187 $file_backup = sprintf('%s.%s.gz', $file_path, date('YmdHis')); 188 189 $content_old = io_readFile($file_path); 190 $content_new = cleanText($INPUT->post->str('content')); 191 192 if (md5($content_old) === md5($content_new)) { 193 return false; 194 } 195 196 if (io_saveFile($file_path, $content_new)) { 197 198 if ($this->getConf('backup')) { 199 io_saveFile($file_backup, $content_old); 200 } 201 // Create a backup 202 msg(sprintf($this->getLang('conf_file_save_success'), $file_name), 1); 203 204 } else { 205 msg(sprintf($this->getLang('conf_file_save_fail'), $file_name), -1); 206 } 207 208 } 209 210 public function cmd_wordblock_update() 211 { 212 213 $file_info = $this->getFileInfo(); 214 $blacklist_url = 'https://meta.wikimedia.org/wiki/Spam_blacklist?action=raw'; 215 216 $http = new DokuHTTPClient(); 217 $http->timeout = 25; 218 $http->keep_alive = false; 219 220 $blacklist = $http->get($blacklist_url); 221 $blacklist = trim(preg_replace('/#(.*)$/m', '', $blacklist)); # Remove all comments from file 222 $blacklist = trim(preg_replace('/[\n]+/m', "\n", $blacklist)); # Remove multiple new line 223 224 if (io_saveFile($file_info['local'], $blacklist)) { 225 msg($this->getLang('conf_blacklist_update'), 1); 226 } else { 227 msg($this->getLang('conf_blacklist_failed'), -1); 228 } 229 230 } 231 232 private function help($file) 233 { 234 echo $this->locale_xhtml($file); 235 return true; 236 } 237 238 private function getDefault() 239 { 240 241 $this->getDefaultConfig('default'); 242 $this->getDefaultConfig('protected'); 243 244 } 245 246 private function getDefaultConfig($file) 247 { 248 global $lang; 249 250 $file_info = $this->fileInfo; 251 252 if (!$file_info[$file]) { 253 return; 254 } 255 256 $file_name = $file_info[$file . '_name']; 257 $file_path = $file_info[$file]; 258 $file_lastmod = $file_info[$file . '_last_modify']; 259 260 echo '<div class="config_' . $file . '">'; 261 echo '<h3>' . "$file_name</h3>"; 262 echo '<div class="content">'; 263 echo '<textarea class="edit" rows="15" cols="" disabled="disabled">'; 264 echo hsc(io_readFile($file_path)); 265 echo '</textarea>'; 266 echo '<p class="docInfo small pull-right">'; 267 echo $file_path; 268 echo (file_exists($file_path) ? ' · ' . $lang['lastmod'] . ' ' . $file_lastmod : ''); 269 echo '</p>'; 270 echo '</div>'; 271 echo '</div>'; 272 273 return true; 274 275 } 276 277 private function editForm() 278 { 279 280 global $lang; 281 282 $file_info = $this->fileInfo; 283 $file_path = $file_info['local']; 284 $file_data = (file_exists($file_path) ? io_readFile($file_path) : ''); 285 $file_lastmod = $file_info['local_last_modify']; 286 $file_name = $file_info['local_name']; 287 288 $lng_edit = $this->getLang('conf_edit'); 289 $lng_upd = $this->getLang('conf_blacklist_download'); 290 291 echo "<h3>$lng_edit $file_name</h3>"; 292 293 echo '<form action="" method="post">'; 294 echo '<textarea name="content" class="edit" rows="15" cols="">'; 295 echo $file_data; 296 echo '</textarea>'; 297 298 echo '<p class="docInfo small pull-right">'; 299 echo $file_path; 300 echo (file_exists($file_path) ? ' · ' . $lang['lastmod'] . ' ' . $file_lastmod : ''); 301 echo '</p>'; 302 303 echo '<p> </p>'; 304 305 formSecurityToken(); 306 307 echo '<input type="hidden" name="do" value="admin" />'; 308 echo '<input type="hidden" name="page" value="advanced_config" />'; 309 310 echo '<button type="submit" name="cmd[save]" class="btn btn-primary primary">' . $lang['btn_save'] . '</button> '; 311 312 if ($file_info['tab'] == 'userstyle' || $file_info['file'] == 'userscript') { 313 314 $purge_type = (($file_info['tab'] == 'userstyle') ? 'css' : 'js'); 315 316 echo '<button type="button" class="primary btn btn-default purge-cache" data-purge-msg="' . $this->getLang('conf_cache_purged') . '" data-purge-type="' . $purge_type . '">' . $this->getLang("btn_purge_$purge_type") . '</button> '; 317 318 } 319 320 if ($file_info['file'] == 'wordblock') { 321 echo '<button type="submit" name="cmd[wordblock_update]" class="btn btn-default">' . $lng_upd . '</button> '; 322 } 323 324 echo '<button type="submit" class="btn btn-default">' . $lang['btn_cancel'] . '</button>'; 325 echo '</form>'; 326 327 return true; 328 329 } 330 331 /** 332 * output appropriate html 333 */ 334 public function html() 335 { 336 337 global $INPUT; 338 global $lang; 339 global $conf; 340 global $ID; 341 342 $lang['toc'] = $this->getLang('menu_config'); 343 344 $this->fileInfo = $file_info = $this->getFileInfo(); 345 346 echo '<div id="plugin_advanced_config">'; 347 348 echo $this->locale_xhtml('config/intro'); 349 echo '<p> </p>'; 350 351 if ($current_tab = $this->currentTab()) { 352 353 $tab_label = $this->getTabs(); 354 echo '<h2>' . $tab_label[$current_tab] . '</h2>'; 355 echo '<p><ul class="tabs">'; 356 357 foreach ($this->getTab($current_tab) as $file => $title) { 358 359 $file_class = ''; 360 361 if ($INPUT->str('file') == $file) { 362 $file_class = 'active'; 363 } 364 365 echo '<li class="' . $file_class . '"><a href="' . $this->tabURL($current_tab, array('file' => $file, 'sectok' => getSecurityToken())) . '">' . $title . '</a></li>'; 366 } 367 368 echo '</ul></p>'; 369 370 } 371 372 if ($current_tab == 'config' && !isset($file_info['file'])) { 373 $this->help('config'); 374 } 375 376 if ($current_tab == 'userstyle' && !isset($file_info['file'])) { 377 $this->help('config/userstyle'); 378 } 379 380 if ($current_tab == 'hook' && !isset($file_info['file'])) { 381 $this->help('config/hooks'); 382 } 383 384 if (isset($file_info['file']) && in_array($file_info['file'], $this->allowedFiles)) { 385 386 $this->help($file_info['help']); 387 echo '<p> </p>'; 388 $this->getDefaultConfig('default'); 389 $this->getDefaultConfig('protected'); 390 $this->editForm(); 391 392 } 393 394 echo '</div>'; 395 396 } 397 398 public function getTabs() 399 { 400 401 return array( 402 'config' => $this->getLang('conf_tab_configurations'), 403 'userstyle' => $this->getLang('conf_tab_styles'), 404 'hook' => $this->getLang('conf_tab_hooks'), 405 'other' => $this->getLang('conf_tab_others'), 406 ); 407 } 408 409 public function getTab($tab) 410 { 411 global $conf; 412 global $plugin_controller; 413 414 $current_section = $this->currentTab(); 415 416 // DokuWiki config 417 $toc_configs = array( 418 'acronyms' => $this->getLang('conf_abbrev'), 419 'entities' => $this->getLang('conf_entities'), 420 'interwiki' => $this->getLang('conf_iwiki'), 421 'mime' => $this->getLang('conf_mime'), 422 'smileys' => $this->getLang('conf_smiley'), 423 'scheme' => $this->getLang('conf_scheme'), 424 'wordblock' => $this->getLang('conf_blacklist'), 425 'license' => $this->getLang('conf_license'), 426 'main' => $this->getLang('conf_main'), 427 'manifest' => $this->getLang('conf_manifest'), 428 'plugins' => $this->getLang('conf_plugins'), 429 'styleini' => $this->getLang('conf_styleini'), 430 'userscript' => $this->getLang('conf_ujs'), 431 ); 432 433 // User Style 434 $toc_styles = array( 435 'screen' => 'Screen', 436 'print' => 'Print', 437 'feed' => 'Feed', 438 'all' => 'All', 439 ); 440 441 // Template Hooks 442 $toc_hooks = array( 443 'meta' => 'Meta', 444 'sidebarheader' => $this->getLang('conf_sidebar') . ' (' . $this->getLang('conf_header') . ')', 445 'sidebarfooter' => $this->getLang('conf_sidebar') . ' (' . $this->getLang('conf_footer') . ')', 446 'pageheader' => 'Page (' . $this->getLang('conf_header') . ')', 447 'pagefooter' => 'Page (' . $this->getLang('conf_footer') . ')', 448 'header' => $this->getLang('conf_header'), 449 'footer' => $this->getLang('conf_footer'), 450 ); 451 452 // Other config 453 $toc_others = array( 454 'htaccess' => '.htaccess', 455 ); 456 457 if ($conf['useacl']) { 458 $toc_configs['acl'] = 'ACL'; 459 } 460 461 if ($conf['authtype'] == 'authplain') { 462 $toc_configs['users'] = 'Users'; 463 } 464 465 // Specific Template Hooks 466 switch ($conf['template']) { 467 468 case 'bootstrap3': 469 470 $toc_hooks['topheader'] = $this->getLang('conf_topheader'); 471 $toc_hooks['rightsidebarheader'] = $this->getLang('conf_rsidebar') . ' (' . $this->getLang('conf_header') . ')'; 472 $toc_hooks['rightsidebarfooter'] = $this->getLang('conf_rsidebar') . ' (' . $this->getLang('conf_footer') . ')'; 473 $toc_hooks['social'] = 'Social'; 474 475 $toc_others['bootstrap3.themes.conf'] = 'Bootstrap3 NS Themes'; 476 477 break; 478 479 } 480 481 $plugin_list = $plugin_controller->getList('', true); 482 483 if (!is_array($plugin_list)) { 484 $plugin_list = array(); 485 } 486 487 $toc_plugins = array(); 488 489 foreach ($plugin_list as $plugin) { 490 491 switch ($plugin) { 492 case 'explain': 493 $toc_plugins['explain.conf'] = 'Explain'; 494 break; 495 } 496 497 } 498 499 $toc_items = array( 500 'config' => $toc_configs, 501 'userstyle' => $toc_styles, 502 'hook' => $toc_hooks, 503 'other' => $toc_others, 504 'plugin' => $toc_plugins, 505 ); 506 507 if ($current_section) { 508 $this->allowedFiles = array_keys($toc_items[$current_section]); 509 } 510 511 if (!isset($toc_items[$tab])) { 512 return array(); 513 } 514 515 return $toc_items[$tab]; 516 } 517 518 /** 519 * Create an URL 520 * 521 * @param string $tab tab to load, empty for current tab 522 * @param array $params associative array of parameter to set 523 * @param string $sep seperator to build the URL 524 * @param bool $absolute create absolute URLs? 525 * @return string 526 */ 527 public function tabURL($tab = '', $params = array(), $sep = '&', $absolute = false) 528 { 529 global $ID; 530 531 $defaults = array( 532 'do' => 'admin', 533 'page' => 'advanced_config', 534 'tab' => $tab, 535 ); 536 537 return wl($ID, array_merge($defaults, $params), $absolute, $sep); 538 } 539 540 public function currentTab() 541 { 542 global $INPUT; 543 544 $tab = $INPUT->str('tab'); 545 546 if (in_array($tab, array_keys($this->getTabs()))) { 547 return $tab; 548 } 549 550 return null; 551 } 552 553 public function getTOC() 554 { 555 global $ID; 556 557 foreach ($this->getTabs() as $section => $section_title) { 558 559 $toc[] = array( 560 'link' => wl($ID, array( 561 'do' => 'admin', 562 'page' => 'advanced_config', 563 'tab' => $section) 564 ), 565 'title' => $section_title, 566 'level' => 1, 567 'type' => 'ul', 568 ); 569 570 } 571 572 return $toc; 573 } 574 575} 576