1 <?php
2 
3 /**
4  * Configuration Manager admin plugin
5  *
6  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7  * @author     Christopher Smith <chris@jalakai.co.uk>
8  * @author     Ben Coburn <btcoburn@silicodon.net>
9  */
10 
11 use dokuwiki\Extension\AdminPlugin;
12 use dokuwiki\plugin\config\core\Configuration;
13 use dokuwiki\plugin\config\core\Setting\Setting;
14 use dokuwiki\plugin\config\core\Setting\SettingFieldset;
15 use dokuwiki\plugin\config\core\Setting\SettingHidden;
16 
17 /**
18  * All DokuWiki plugins to extend the admin function
19  * need to inherit from this class
20  */
21 class admin_plugin_config extends AdminPlugin
22 {
23     protected const IMGDIR = DOKU_BASE . 'lib/plugins/config/images/';
24 
25     /** @var Configuration */
26     protected $configuration;
27 
28     /** @var bool were there any errors in the submitted data? */
29     protected $hasErrors = false;
30 
31     /** @var bool have the settings translations been loaded? */
32     protected $promptsLocalized = false;
33 
34 
35     /**
36      * handle user request
37      */
38     public function handle()
39     {
40         global $ID, $INPUT;
41 
42         // always initialize the configuration
43         $this->configuration = new Configuration();
44 
45         if (!$INPUT->bool('save') || !checkSecurityToken()) {
46             return;
47         }
48 
49         // don't go any further if the configuration is locked
50         if ($this->configuration->isLocked()) return;
51 
52         // update settings and redirect of successful
53         $ok = $this->configuration->updateSettings($INPUT->arr('config'));
54         if ($ok) { // no errors
55             try {
56                 if ($this->configuration->hasChanged()) {
57                     $this->configuration->save();
58                 } else {
59                     $this->configuration->touch();
60                 }
61                 msg($this->getLang('updated'), 1);
62             } catch (Exception $e) {
63                 msg($this->getLang('error'), -1);
64             }
65             send_redirect(wl($ID, ['do' => 'admin', 'page' => 'config'], true, '&'));
66         } else {
67             $this->hasErrors = true;
68             msg($this->getLang('error'), -1);
69         }
70     }
71 
72     /**
73      * output appropriate html
74      */
75     public function html()
76     {
77         $allow_debug = $GLOBALS['conf']['allowdebug']; // avoid global $conf; here.
78         global $lang;
79         global $ID;
80 
81         $this->setupLocale(true);
82 
83         echo $this->locale_xhtml('intro');
84 
85         echo '<div id="config__manager">';
86 
87         if ($this->configuration->isLocked()) {
88             echo '<div class="info">' . $this->getLang('locked') . '</div>';
89         }
90 
91         // POST to script() instead of wl($ID) so config manager still works if
92         // rewrite config is broken. Add $ID as hidden field to remember
93         // current ID in most cases.
94         echo '<form id="dw__configform" action="' . script() . '" method="post" autocomplete="off">';
95         echo '<div class="no"><input type="hidden" name="id" value="' . $ID . '" /></div>';
96         formSecurityToken();
97         $this->printH1('dokuwiki_settings', $this->getLang('_header_dokuwiki'));
98 
99         $in_fieldset = false;
100         $first_plugin_fieldset = true;
101         $first_template_fieldset = true;
102         foreach ($this->configuration->getSettings() as $setting) {
103             if ($setting instanceof SettingHidden) {
104                 continue;
105             } elseif ($setting instanceof SettingFieldset) {
106                 // config setting group
107                 if ($in_fieldset) {
108                     echo '</table>';
109                     echo '</div>';
110                     echo '</fieldset>';
111                 } else {
112                     $in_fieldset = true;
113                 }
114                 if ($first_plugin_fieldset && $setting->getType() == 'plugin') {
115                     $this->printH1('plugin_settings', $this->getLang('_header_plugin'));
116                     $first_plugin_fieldset = false;
117                 } elseif ($first_template_fieldset && $setting->getType() == 'template') {
118                     $this->printH1('template_settings', $this->getLang('_header_template'));
119                     $first_template_fieldset = false;
120                 }
121                 echo '<fieldset id="' . $setting->getKey() . '">';
122                 echo '<legend>' . $setting->prompt($this) . '</legend>';
123                 echo '<div class="table">';
124                 echo '<table class="inline">';
125             } else {
126                 // config settings
127                 [$label, $input] = $setting->html($this, $this->hasErrors);
128 
129                 $class = $setting->isDefault()
130                     ? ' class="default"'
131                     : ($setting->isProtected() ? ' class="protected"' : '');
132                 $error = $setting->hasError()
133                     ? ' class="value error"'
134                     : ' class="value"';
135                 $icon = $setting->caution()
136                     ? '<img src="' . self::IMGDIR . $setting->caution() . '.png" ' .
137                     'alt="' . $setting->caution() . '" title="' . $this->getLang($setting->caution()) . '" />'
138                     : '';
139 
140                 echo '<tr' . $class . '>';
141                 echo '<td class="label">';
142                 echo '<span class="outkey">' . $setting->getPrettyKey() . '</span>';
143                 echo $icon . $label;
144                 echo '</td>';
145                 echo '<td' . $error . '>' . $input . '</td>';
146                 echo '</tr>';
147             }
148         }
149 
150         echo '</table>';
151         echo '</div>';
152         if ($in_fieldset) {
153             echo '</fieldset>';
154         }
155 
156         // show undefined settings list
157         $undefined_settings = $this->configuration->getUndefined();
158         if ($allow_debug && !empty($undefined_settings)) {
159             /**
160              * Callback for sorting settings
161              *
162              * @param Setting $a
163              * @param Setting $b
164              * @return int if $a is lower/equal/higher than $b
165              */
166             function settingNaturalComparison($a, $b)
167             {
168                 return strnatcmp($a->getKey(), $b->getKey());
169             }
170 
171             usort($undefined_settings, 'settingNaturalComparison');
172             $this->printH1('undefined_settings', $this->getLang('_header_undefined'));
173             echo '<fieldset>';
174             echo '<div class="table">';
175             echo '<table class="inline">';
176             foreach ($undefined_settings as $setting) {
177                 [$label, $input] = $setting->html($this);
178                 echo '<tr>';
179                 echo '<td class="label">' . $label . '</td>';
180                 echo '<td>' . $input . '</td>';
181                 echo '</tr>';
182             }
183             echo '</table>';
184             echo '</div>';
185             echo '</fieldset>';
186         }
187 
188         // finish up form
189         echo '<p>';
190         echo '<input type="hidden" name="do"     value="admin" />';
191         echo '<input type="hidden" name="page"   value="config" />';
192 
193         if (!$this->configuration->isLocked()) {
194             echo '<input type="hidden" name="save"   value="1" />';
195             echo '<button type="submit" name="submit" accesskey="s">' . $lang['btn_save'] . '</button>';
196             echo '<button type="reset">' . $lang['btn_reset'] . '</button>';
197         }
198 
199         echo '</p>';
200 
201         echo '</form>';
202         echo '</div>';
203     }
204 
205     /**
206      * @param bool $prompts
207      */
208     public function setupLocale($prompts = false)
209     {
210         parent::setupLocale();
211         if (!$prompts || $this->promptsLocalized) return;
212         $this->lang = array_merge($this->lang, $this->configuration->getLangs());
213         $this->promptsLocalized = true;
214     }
215 
216     /**
217      * Generates a two-level table of contents for the config plugin.
218      *
219      * @author Ben Coburn <btcoburn@silicodon.net>
220      *
221      * @return array
222      */
223     public function getTOC()
224     {
225         $this->setupLocale(true);
226 
227         $allow_debug = $GLOBALS['conf']['allowdebug']; // avoid global $conf; here.
228         $toc = [];
229         $check = false;
230 
231         // gather settings data into three sub arrays
232         $labels = ['dokuwiki' => [], 'plugin' => [], 'template' => []];
233         foreach ($this->configuration->getSettings() as $setting) {
234             if ($setting instanceof SettingFieldset) {
235                 $labels[$setting->getType()][] = $setting;
236             }
237         }
238 
239         // top header
240         $title = $this->getLang('_configuration_manager');
241         $toc[] = html_mktocitem(sectionID($title, $check), $title, 1);
242 
243         // main entries
244         foreach (['dokuwiki', 'plugin', 'template'] as $section) {
245             if (empty($labels[$section])) continue; // no entries, skip
246 
247             // create main header
248             $toc[] = html_mktocitem(
249                 $section . '_settings',
250                 $this->getLang('_header_' . $section),
251                 1
252             );
253 
254             // create sub headers
255             foreach ($labels[$section] as $setting) {
256                 /** @var SettingFieldset $setting */
257                 $name = $setting->prompt($this);
258                 $toc[] = html_mktocitem($setting->getKey(), $name, 2);
259             }
260         }
261 
262         // undefined settings if allowed
263         if (count($this->configuration->getUndefined()) && $allow_debug) {
264             $toc[] = html_mktocitem('undefined_settings', $this->getLang('_header_undefined'), 1);
265         }
266 
267         return $toc;
268     }
269 
270     /**
271      * @param string $id
272      * @param string $text
273      */
274     protected function printH1($id, $text)
275     {
276         echo '<h1 id="' . $id . '">' . $text . '</h1>';
277     }
278 
279     /**
280      * Adds a translation to this plugin's language array
281      *
282      * Used by some settings to set up dynamic translations
283      *
284      * @param string $key
285      * @param string $value
286      */
287     public function addLang($key, $value)
288     {
289         if (!$this->localised) $this->setupLocale();
290         $this->lang[$key] = $value;
291     }
292 }
293