1<?php
2
3namespace dokuwiki\plugin\config\core\Setting;
4
5use dokuwiki\plugin\config\core\Configuration;
6
7/**
8 * Class Setting
9 */
10class 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