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>&nbsp;</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>&nbsp;</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>&nbsp;</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 = '&amp;', $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