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