1 <?php
2 
3 namespace dokuwiki\plugin\config\core\Setting;
4 
5 use dokuwiki\plugin\config\core\Configuration;
6 
7 /**
8  * Class Setting
9  */
10 class Setting
11 {
12     /** @var string unique identifier of this setting */
13     protected $key = '';
14 
15     /** @var mixed the default value of this setting */
16     protected $default;
17     /** @var mixed the local value of this setting */
18     protected $local;
19     /** @var mixed the protected value of this setting */
20     protected $protected;
21 
22     /** @var array valid alerts, images matching the alerts are in the plugin's images directory */
23     protected static $validCautions = ['warning', 'danger', 'security'];
24 
25     protected $pattern = '';
26     protected $error = false;            // only used by those classes which error check
27     protected $input;             // only used by those classes which error check
28     protected $caution;           // used by any setting to provide an alert along with the setting
29 
30     /**
31      * Constructor.
32      *
33      * The given parameters will be set up as class properties
34      *
35      * @see initialize() to set the actual value of the setting
36      *
37      * @param string $key
38      * @param array|null $params array with metadata of setting
39      */
40     public function __construct($key, $params = null)
41     {
42         $this->key = $key;
43 
44         if (is_array($params)) {
45             foreach ($params as $property => $value) {
46                 $property = trim($property, '_'); // we don't use underscores anymore
47                 $this->$property = $value;
48             }
49         }
50     }
51 
52     /**
53      * Set the current values for the setting $key
54      *
55      * This is used to initialize the setting with the data read form the config files.
56      *
57      * @see update() to set a new value
58      * @param mixed $default default setting value
59      * @param mixed $local local setting value
60      * @param mixed $protected protected setting value
61      */
62     public function initialize($default = null, $local = null, $protected = null)
63     {
64         $this->default = $this->cleanValue($default);
65         $this->local = $this->cleanValue($local);
66         $this->protected = $this->cleanValue($protected);
67     }
68 
69     /**
70      * update changed setting with validated user provided value $input
71      * - if changed value fails validation check, save it to $this->input (to allow echoing later)
72      * - if changed value passes validation check, set $this->local to the new value
73      *
74      * @param  mixed $input the new value
75      * @return boolean          true if changed, false otherwise
76      */
77     public function update($input)
78     {
79         if (is_null($input)) return false;
80         if ($this->isProtected()) return false;
81         $input = $this->cleanValue($input);
82 
83         $value = is_null($this->local) ? $this->default : $this->local;
84         if ($value == $input) return false;
85 
86         // validate new value
87         if ($this->pattern && !preg_match($this->pattern, $input)) {
88             $this->error = true;
89             $this->input = $input;
90             return false;
91         }
92 
93         // update local copy of this setting with new value
94         $this->local = $input;
95 
96         // setting ready for update
97         return true;
98     }
99 
100     /**
101      * Clean a value read from a config before using it internally
102      *
103      * Default implementation returns $value as is. Subclasses can override.
104      * Note: null should always be returned as null!
105      *
106      * This is applied in initialize() and update()
107      *
108      * @param mixed $value
109      * @return mixed
110      */
111     protected function cleanValue($value)
112     {
113         return $value;
114     }
115 
116     /**
117      * Should this type of config have a default?
118      *
119      * @return bool
120      */
121     public function shouldHaveDefault()
122     {
123         return true;
124     }
125 
126     /**
127      * Get this setting's unique key
128      *
129      * @return string
130      */
131     public function getKey()
132     {
133         return $this->key;
134     }
135 
136     /**
137      * Get the key of this setting marked up human readable
138      *
139      * @param bool $url link to dokuwiki.org manual?
140      * @return string
141      */
142     public function getPrettyKey($url = true)
143     {
144         $out = str_replace(Configuration::KEYMARKER, "»", $this->key);
145         if ($url && !strstr($out, '»')) {//provide no urls for plugins, etc.
146             if ($out == 'start') {
147                 // exception, because this config name is clashing with our actual start page
148                 return '<a href="https://www.dokuwiki.org/config:startpage">' . $out . '</a>';
149             } else {
150                 return '<a href="https://www.dokuwiki.org/config:' . $out . '">' . $out . '</a>';
151             }
152         }
153         return $out;
154     }
155 
156     /**
157      * Returns setting key as an array key separator
158      *
159      * This is used to create form output
160      *
161      * @return string key
162      */
163     public function getArrayKey()
164     {
165         return str_replace(Configuration::KEYMARKER, "']['", $this->key);
166     }
167 
168     /**
169      * What type of configuration is this
170      *
171      * Returns one of
172      *
173      * 'plugin' for plugin configuration
174      * 'template' for template configuration
175      * 'dokuwiki' for core configuration
176      *
177      * @return string
178      */
179     public function getType()
180     {
181         if (str_starts_with($this->getKey(), 'plugin' . Configuration::KEYMARKER)) {
182             return 'plugin';
183         } elseif (str_starts_with($this->getKey(), 'tpl' . Configuration::KEYMARKER)) {
184             return 'template';
185         } else {
186             return 'dokuwiki';
187         }
188     }
189 
190     /**
191      * Build html for label and input of setting
192      *
193      * @param \admin_plugin_config $plugin object of config plugin
194      * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
195      * @return string[] with content array(string $label_html, string $input_html)
196      */
197     public function html(\admin_plugin_config $plugin, $echo = false)
198     {
199         $disable = '';
200 
201         if ($this->isProtected()) {
202             $value = $this->protected;
203             $disable = 'disabled="disabled"';
204         } elseif ($echo && $this->error) {
205             $value = $this->input;
206         } else {
207             $value = is_null($this->local) ? $this->default : $this->local;
208         }
209 
210         $key = htmlspecialchars($this->key);
211         $value = formText($value);
212 
213         $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>';
214         $input = '<textarea rows="3" cols="40" id="config___' . $key .
215             '" name="config[' . $key . ']" class="edit" ' . $disable . '>' . $value . '</textarea>';
216         return [$label, $input];
217     }
218 
219     /**
220      * Should the current local value be saved?
221      *
222      * @see out() to run when this returns true
223      * @return bool
224      */
225     public function shouldBeSaved()
226     {
227         if ($this->isProtected()) return false;
228         if ($this->local === null) return false;
229         if ($this->default == $this->local) return false;
230         return true;
231     }
232 
233     /**
234      * Escaping
235      *
236      * @param string $string
237      * @return string
238      */
239     protected function escape($string)
240     {
241         $tr = ["\\" => '\\\\', "'" => '\\\''];
242         return "'" . strtr(cleanText($string), $tr) . "'";
243     }
244 
245     /**
246      * Generate string to save local setting value to file according to $fmt
247      *
248      * @see shouldBeSaved() to check if this should be called
249      * @param string $var name of variable
250      * @param string $fmt save format
251      * @return string
252      */
253     public function out($var, $fmt = 'php')
254     {
255         if ($fmt != 'php') return '';
256 
257         if (is_array($this->local)) {
258             $value = 'array(' . implode(', ', array_map([$this, 'escape'], $this->local)) . ')';
259         } else {
260             $value = $this->escape($this->local);
261         }
262 
263         $out = '$' . $var . "['" . $this->getArrayKey() . "'] = $value;\n";
264 
265         return $out;
266     }
267 
268     /**
269      * Returns the localized prompt
270      *
271      * @param \admin_plugin_config $plugin object of config plugin
272      * @return string text
273      */
274     public function prompt(\admin_plugin_config $plugin)
275     {
276         $prompt = $plugin->getLang($this->key);
277         if (!$prompt) $prompt = htmlspecialchars(str_replace(['____', '_'], ' ', $this->key));
278         return $prompt;
279     }
280 
281     /**
282      * Is setting protected
283      *
284      * @return bool
285      */
286     public function isProtected()
287     {
288         return !is_null($this->protected);
289     }
290 
291     /**
292      * Is setting the default?
293      *
294      * @return bool
295      */
296     public function isDefault()
297     {
298         return !$this->isProtected() && is_null($this->local);
299     }
300 
301     /**
302      * Has an error?
303      *
304      * @return bool
305      */
306     public function hasError()
307     {
308         return $this->error;
309     }
310 
311     /**
312      * Returns caution
313      *
314      * @return false|string caution string, otherwise false for invalid caution
315      */
316     public function caution()
317     {
318         if (empty($this->caution)) return false;
319         if (!in_array($this->caution, Setting::$validCautions)) {
320             throw new \RuntimeException(
321                 'Invalid caution string (' . $this->caution . ') in metadata for setting "' . $this->key . '"'
322             );
323         }
324         return $this->caution;
325     }
326 }
327