Completed
Push — master ( 594b16...fb8e82 )
by Andreas
03:18
created

setting_numericopt::update()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Configuration Class and generic setting classes
4
 *
5
 * @author  Chris Smith <[email protected]>
6
 * @author  Ben Coburn <[email protected]>
7
 */
8
9
10
if(!defined('CM_KEYMARKER')) define('CM_KEYMARKER','____');
11
12
if (!class_exists('configuration')) {
13
    /**
14
     * Class configuration
15
     */
16
    class configuration {
17
18
        var $_name = 'conf';           // name of the config variable found in the files (overridden by $config['varname'])
19
        var $_format = 'php';          // format of the config file, supported formats - php (overridden by $config['format'])
20
        var $_heading = '';            // heading string written at top of config file - don't include comment indicators
21
        var $_loaded = false;          // set to true after configuration files are loaded
22
        var $_metadata = array();      // holds metadata describing the settings
23
        /** @var setting[]  */
24
        var $setting = array();        // array of setting objects
25
        var $locked = false;           // configuration is considered locked if it can't be updated
26
        var $show_disabled_plugins = false;
27
28
        // configuration filenames
29
        var $_default_files  = array();
30
        var $_local_files = array();      // updated configuration is written to the first file
31
        var $_protected_files = array();
32
33
        var $_plugin_list = null;
34
35
        /**
36
         * constructor
37
         *
38
         * @param string $datafile path to config metadata file
39
         */
40
        public function __construct($datafile) {
41
            global $conf, $config_cascade;
42
43
            if (!file_exists($datafile)) {
44
                msg('No configuration metadata found at - '.htmlspecialchars($datafile),-1);
45
                return;
46
            }
47
            $meta = array();
48
            include($datafile);
49
50
            if (isset($config['varname'])) $this->_name = $config['varname'];
0 ignored issues
show
Bug introduced by
The variable $config does not exist. Did you mean $config_cascade?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
51
            if (isset($config['format'])) $this->_format = $config['format'];
52
            if (isset($config['heading'])) $this->_heading = $config['heading'];
53
54
            $this->_default_files = $config_cascade['main']['default'];
55
            $this->_local_files = $config_cascade['main']['local'];
56
            $this->_protected_files = $config_cascade['main']['protected'];
57
58
            $this->locked = $this->_is_locked();
59
            $this->_metadata = array_merge($meta, $this->get_plugintpl_metadata($conf['template']));
60
            $this->retrieve_settings();
61
        }
62
63
        /**
64
         * Retrieve and stores settings in setting[] attribute
65
         */
66
        public function retrieve_settings() {
67
            global $conf;
68
            $no_default_check = array('setting_fieldset', 'setting_undefined', 'setting_no_class');
69
70
            if (!$this->_loaded) {
71
                $default = array_merge($this->get_plugintpl_default($conf['template']), $this->_read_config_group($this->_default_files));
72
                $local = $this->_read_config_group($this->_local_files);
73
                $protected = $this->_read_config_group($this->_protected_files);
74
75
                $keys = array_merge(array_keys($this->_metadata),array_keys($default), array_keys($local), array_keys($protected));
76
                $keys = array_unique($keys);
77
78
                $param = null;
79
                foreach ($keys as $key) {
80
                    if (isset($this->_metadata[$key])) {
81
                        $class = $this->_metadata[$key][0];
82
83
                        if($class && class_exists('setting_'.$class)){
84
                            $class = 'setting_'.$class;
85
                        } else {
86
                            if($class != '') {
87
                                $this->setting[] = new setting_no_class($key,$param);
88
                            }
89
                            $class = 'setting';
90
                        }
91
92
                        $param = $this->_metadata[$key];
93
                        array_shift($param);
94
                    } else {
95
                        $class = 'setting_undefined';
96
                        $param = null;
97
                    }
98
99
                    if (!in_array($class, $no_default_check) && !isset($default[$key])) {
100
                        $this->setting[] = new setting_no_default($key,$param);
101
                    }
102
103
                    $this->setting[$key] = new $class($key,$param);
104
105
                    $d = array_key_exists($key, $default) ? $default[$key] : null;
106
                    $l = array_key_exists($key, $local) ? $local[$key] : null;
107
                    $p = array_key_exists($key, $protected) ? $protected[$key] : null;
108
109
                    $this->setting[$key]->initialize($d,$l,$p);
110
                }
111
112
                $this->_loaded = true;
113
            }
114
        }
115
116
        /**
117
         * Stores setting[] array to file
118
         *
119
         * @param string $id     Name of plugin, which saves the settings
120
         * @param string $header Text at the top of the rewritten settings file
121
         * @param bool $backup   backup current file? (remove any existing backup)
122
         * @return bool succesful?
123
         */
124
        public function save_settings($id, $header='', $backup=true) {
125
            global $conf;
126
127
            if ($this->locked) return false;
128
129
            // write back to the last file in the local config cascade
130
            $file = end($this->_local_files);
131
132
            // backup current file (remove any existing backup)
133
            if (file_exists($file) && $backup) {
134
                if (file_exists($file.'.bak')) @unlink($file.'.bak');
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
135
                if (!io_rename($file, $file.'.bak')) return false;
136
            }
137
138
            if (!$fh = @fopen($file, 'wb')) {
139
                io_rename($file.'.bak', $file);     // problem opening, restore the backup
140
                return false;
141
            }
142
143
            if (empty($header)) $header = $this->_heading;
144
145
            $out = $this->_out_header($id,$header);
146
147
            foreach ($this->setting as $setting) {
148
                $out .= $setting->out($this->_name, $this->_format);
149
            }
150
151
            $out .= $this->_out_footer();
152
153
            @fwrite($fh, $out);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
154
            fclose($fh);
155
            if($conf['fperm']) chmod($file, $conf['fperm']);
156
            return true;
157
        }
158
159
        /**
160
         * Update last modified time stamp of the config file
161
         *
162
         * @return bool
163
         */
164
        public function touch_settings(){
165
            if ($this->locked) return false;
166
            $file = end($this->_local_files);
167
            return @touch($file);
168
        }
169
170
        /**
171
         * Read and merge given config files
172
         *
173
         * @param array $files file paths
174
         * @return array config settings
175
         */
176
        protected function _read_config_group($files) {
177
            $config = array();
178
            foreach ($files as $file) {
179
                $config = array_merge($config, $this->_read_config($file));
180
            }
181
182
            return $config;
183
        }
184
185
        /**
186
         * Return an array of config settings
187
         *
188
         * @param string $file file path
189
         * @return array config settings
190
         */
191
        function _read_config($file) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
192
193
            if (!$file) return array();
194
195
            $config = array();
196
197
            if ($this->_format == 'php') {
198
199
                if(file_exists($file)){
200
                    $contents = @php_strip_whitespace($file);
201
                }else{
202
                    $contents = '';
203
                }
204
                $pattern = '/\$'.$this->_name.'\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);(?=[^;]*(?:\$'.$this->_name.'|$))/s';
205
                $matches=array();
206
                preg_match_all($pattern,$contents,$matches,PREG_SET_ORDER);
207
208
                for ($i=0; $i<count($matches); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
209
                    $value = $matches[$i][2];
210
211
                    // correct issues with the incoming data
212
                    // FIXME ... for now merge multi-dimensional array indices using ____
213
                    $key = preg_replace('/.\]\[./',CM_KEYMARKER,$matches[$i][1]);
214
215
                    // handle arrays
216
                    if(preg_match('/^array ?\((.*)\)/', $value, $match)){
217
                        $arr = explode(',', $match[1]);
218
219
                        // remove quotes from quoted strings & unescape escaped data
220
                        $len = count($arr);
221
                        for($j=0; $j<$len; $j++){
222
                            $arr[$j] = trim($arr[$j]);
223
                            $arr[$j] = $this->_readValue($arr[$j]);
224
                        }
225
226
                        $value = $arr;
227
                    }else{
228
                        $value = $this->_readValue($value);
229
                    }
230
231
                    $config[$key] = $value;
232
                }
233
            }
234
235
            return $config;
236
        }
237
238
        /**
239
         * Convert php string into value
240
         *
241
         * @param string $value
242
         * @return bool|string
243
         */
244
        protected function _readValue($value) {
245
            $removequotes_pattern = '/^(\'|")(.*)(?<!\\\\)\1$/s';
246
            $unescape_pairs = array(
247
                '\\\\' => '\\',
248
                '\\\'' => '\'',
249
                '\\"' => '"'
250
            );
251
252
            if($value == 'true') {
253
                $value = true;
254
            } elseif($value == 'false') {
255
                $value = false;
256
            } else {
257
                // remove quotes from quoted strings & unescape escaped data
258
                $value = preg_replace($removequotes_pattern,'$2',$value);
259
                $value = strtr($value, $unescape_pairs);
260
            }
261
            return $value;
262
        }
263
264
        /**
265
         * Returns header of rewritten settings file
266
         *
267
         * @param string $id plugin name of which generated this output
268
         * @param string $header additional text for at top of the file
269
         * @return string text of header
270
         */
271
        protected function _out_header($id, $header) {
272
            $out = '';
273
            if ($this->_format == 'php') {
274
                $out .= '<'.'?php'."\n".
275
                      "/*\n".
276
                      " * ".$header."\n".
277
                      " * Auto-generated by ".$id." plugin\n".
278
                      " * Run for user: ".$_SERVER['REMOTE_USER']."\n".
279
                      " * Date: ".date('r')."\n".
280
                      " */\n\n";
281
            }
282
283
            return $out;
284
        }
285
286
        /**
287
         * Returns footer of rewritten settings file
288
         *
289
         * @return string text of footer
290
         */
291
        protected function _out_footer() {
292
            $out = '';
293
            if ($this->_format == 'php') {
294
                $out .= "\n// end auto-generated content\n";
295
            }
296
297
            return $out;
298
        }
299
300
        /**
301
         * Configuration is considered locked if there is no local settings filename
302
         * or the directory its in is not writable or the file exists and is not writable
303
         *
304
         * @return bool true: locked, false: writable
305
         */
306
        protected function _is_locked() {
307
            if (!$this->_local_files) return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_local_files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
308
309
            $local = $this->_local_files[0];
310
311
            if (!is_writable(dirname($local))) return true;
312
            if (file_exists($local) && !is_writable($local)) return true;
313
314
            return false;
315
        }
316
317
        /**
318
         * not used ... conf's contents are an array!
319
         * reduce any multidimensional settings to one dimension using CM_KEYMARKER
320
         *
321
         * @param $conf
322
         * @param string $prefix
323
         * @return array
324
         */
325
        protected function _flatten($conf,$prefix='') {
326
327
            $out = array();
328
329
            foreach($conf as $key => $value) {
330
                if (!is_array($value)) {
331
                    $out[$prefix.$key] = $value;
332
                    continue;
333
                }
334
335
                $tmp = $this->_flatten($value,$prefix.$key.CM_KEYMARKER);
336
                $out = array_merge($out,$tmp);
337
            }
338
339
            return $out;
340
        }
341
342
        /**
343
         * Returns array of plugin names
344
         *
345
         * @return array plugin names
346
         * @triggers PLUGIN_CONFIG_PLUGINLIST event
347
         */
348
        function get_plugin_list() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
349
            if (is_null($this->_plugin_list)) {
350
                $list = plugin_list('',$this->show_disabled_plugins);
351
352
                // remove this plugin from the list
353
                $idx = array_search('config',$list);
354
                unset($list[$idx]);
355
356
                trigger_event('PLUGIN_CONFIG_PLUGINLIST',$list);
357
                $this->_plugin_list = $list;
358
            }
359
360
            return $this->_plugin_list;
361
        }
362
363
        /**
364
         * load metadata for plugin and template settings
365
         *
366
         * @param string $tpl name of active template
367
         * @return array metadata of settings
368
         */
369
        function get_plugintpl_metadata($tpl){
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
370
            $file     = '/conf/metadata.php';
371
            $class    = '/conf/settings.class.php';
372
            $metadata = array();
373
374
            foreach ($this->get_plugin_list() as $plugin) {
375
                $plugin_dir = plugin_directory($plugin);
376
                if (file_exists(DOKU_PLUGIN.$plugin_dir.$file)){
377
                    $meta = array();
378
                    @include(DOKU_PLUGIN.$plugin_dir.$file);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
379
                    @include(DOKU_PLUGIN.$plugin_dir.$class);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
380
                    if (!empty($meta)) {
381
                        $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.'plugin_settings_name'] = array('fieldset');
382
                    }
383
                    foreach ($meta as $key => $value){
384
                        if ($value[0]=='fieldset') { continue; } //plugins only get one fieldset
385
                        $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value;
386
                    }
387
                }
388
            }
389
390
            // the same for the active template
391
            if (file_exists(tpl_incdir().$file)){
392
                $meta = array();
393
                @include(tpl_incdir().$file);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
394
                @include(tpl_incdir().$class);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
395
                if (!empty($meta)) {
396
                    $metadata['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.'template_settings_name'] = array('fieldset');
397
                }
398
                foreach ($meta as $key => $value){
399
                    if ($value[0]=='fieldset') { continue; } //template only gets one fieldset
400
                    $metadata['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value;
401
                }
402
            }
403
404
            return $metadata;
405
        }
406
407
        /**
408
         * Load default settings for plugins and templates
409
         *
410
         * @param string $tpl name of active template
411
         * @return array default settings
412
         */
413
        function get_plugintpl_default($tpl){
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
414
            $file    = '/conf/default.php';
415
            $default = array();
416
417
            foreach ($this->get_plugin_list() as $plugin) {
418
                $plugin_dir = plugin_directory($plugin);
419
                if (file_exists(DOKU_PLUGIN.$plugin_dir.$file)){
420
                    $conf = $this->_read_config(DOKU_PLUGIN.$plugin_dir.$file);
421
                    foreach ($conf as $key => $value){
422
                        $default['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value;
423
                    }
424
                }
425
            }
426
427
            // the same for the active template
428
            if (file_exists(tpl_incdir().$file)){
429
                $conf = $this->_read_config(tpl_incdir().$file);
430
                foreach ($conf as $key => $value){
431
                    $default['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value;
432
                }
433
            }
434
435
            return $default;
436
        }
437
438
    }
439
}
440
441
if (!class_exists('setting')) {
442
    /**
443
     * Class setting
444
     */
445
    class setting {
446
447
        var $_key = '';
448
        var $_default = null;
449
        var $_local = null;
450
        var $_protected = null;
451
452
        var $_pattern = '';
453
        var $_error = false;            // only used by those classes which error check
454
        var $_input = null;             // only used by those classes which error check
455
        var $_caution = null;           // used by any setting to provide an alert along with the setting
456
                                        // valid alerts, 'warning', 'danger', 'security'
457
                                        // images matching the alerts are in the plugin's images directory
458
459
        static protected $_validCautions = array('warning','danger','security');
460
461
        /**
462
         * @param string $key
463
         * @param array|null $params array with metadata of setting
464
         */
465
        public function __construct($key, $params=null) {
466
            $this->_key = $key;
467
468
            if (is_array($params)) {
469
                foreach($params as $property => $value) {
470
                    $this->$property = $value;
471
                }
472
            }
473
        }
474
475
        /**
476
         * Receives current values for the setting $key
477
         *
478
         * @param mixed $default   default setting value
479
         * @param mixed $local     local setting value
480
         * @param mixed $protected protected setting value
481
         */
482
        public function initialize($default, $local, $protected) {
483
            if (isset($default)) $this->_default = $default;
484
            if (isset($local)) $this->_local = $local;
485
            if (isset($protected)) $this->_protected = $protected;
486
        }
487
488
        /**
489
         * update changed setting with user provided value $input
490
         * - if changed value fails error check, save it to $this->_input (to allow echoing later)
491
         * - if changed value passes error check, set $this->_local to the new value
492
         *
493
         * @param  mixed   $input   the new value
494
         * @return boolean          true if changed, false otherwise (also on error)
495
         */
496
        public function update($input) {
497
            if (is_null($input)) return false;
498
            if ($this->is_protected()) return false;
499
500
            $value = is_null($this->_local) ? $this->_default : $this->_local;
501
            if ($value == $input) return false;
502
503
            if ($this->_pattern && !preg_match($this->_pattern,$input)) {
504
                $this->_error = true;
505
                $this->_input = $input;
506
                return false;
507
            }
508
509
            $this->_local = $input;
510
            return true;
511
        }
512
513
        /**
514
         * Build html for label and input of setting
515
         *
516
         * @param admin_plugin_config $plugin object of config plugin
517
         * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
518
         * @return string[] with content array(string $label_html, string $input_html)
519
         */
520
        public function html(admin_plugin_config $plugin, $echo=false) {
521
            $disable = '';
522
523
            if ($this->is_protected()) {
524
                $value = $this->_protected;
525
                $disable = 'disabled="disabled"';
526
            } else {
527
                if ($echo && $this->_error) {
528
                    $value = $this->_input;
529
                } else {
530
                    $value = is_null($this->_local) ? $this->_default : $this->_local;
531
                }
532
            }
533
534
            $key = htmlspecialchars($this->_key);
535
            $value = formText($value);
536
537
            $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
538
            $input = '<textarea rows="3" cols="40" id="config___'.$key.'" name="config['.$key.']" class="edit" '.$disable.'>'.$value.'</textarea>';
539
            return array($label,$input);
540
        }
541
542
        /**
543
         * Generate string to save setting value to file according to $fmt
544
         *
545
         * @param string $var name of variable
546
         * @param string $fmt save format
547
         * @return string
548
         */
549
        public function out($var, $fmt='php') {
550
551
            if ($this->is_protected()) return '';
552
            if (is_null($this->_local) || ($this->_default == $this->_local)) return '';
553
554
            $out = '';
555
556
            if ($fmt=='php') {
557
                $tr = array("\\" => '\\\\', "'" => '\\\'');
558
559
                $out =  '$'.$var."['".$this->_out_key()."'] = '".strtr( cleanText($this->_local), $tr)."';\n";
560
            }
561
562
            return $out;
563
        }
564
565
        /**
566
         * Returns the localized prompt
567
         *
568
         * @param admin_plugin_config $plugin object of config plugin
569
         * @return string text
570
         */
571
        public function prompt(admin_plugin_config $plugin) {
572
            $prompt = $plugin->getLang($this->_key);
573
            if (!$prompt) $prompt = htmlspecialchars(str_replace(array('____','_'),' ',$this->_key));
574
            return $prompt;
575
        }
576
577
        /**
578
         * Is setting protected
579
         *
580
         * @return bool
581
         */
582
        public function is_protected() { return !is_null($this->_protected); }
583
584
        /**
585
         * Is setting the default?
586
         *
587
         * @return bool
588
         */
589
        public function is_default() { return !$this->is_protected() && is_null($this->_local); }
590
591
        /**
592
         * Has an error?
593
         *
594
         * @return bool
595
         */
596
        public function error() { return $this->_error; }
597
598
        /**
599
         * Returns caution
600
         *
601
         * @return false|string caution string, otherwise false for invalid caution
602
         */
603
        public function caution() {
604
            if (!empty($this->_caution)) {
605
                if (!in_array($this->_caution, setting::$_validCautions)) {
606
                    trigger_error('Invalid caution string ('.$this->_caution.') in metadata for setting "'.$this->_key.'"', E_USER_WARNING);
607
                    return false;
608
                }
609
                return $this->_caution;
610
            }
611
            // compatibility with previous cautionList
612
            // TODO: check if any plugins use; remove
613
            if (!empty($this->_cautionList[$this->_key])) {
0 ignored issues
show
Bug introduced by
The property _cautionList does not seem to exist. Did you mean _caution?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
614
                $this->_caution = $this->_cautionList[$this->_key];
0 ignored issues
show
Bug introduced by
The property _cautionList does not seem to exist. Did you mean _caution?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
615
                unset($this->_cautionList);
616
617
                return $this->caution();
618
            }
619
            return false;
620
        }
621
622
        /**
623
         * Returns setting key, eventually with referer to config: namespace at dokuwiki.org
624
         *
625
         * @param bool $pretty create nice key
626
         * @param bool $url    provide url to config: namespace
627
         * @return string key
628
         */
629
        public function _out_key($pretty=false,$url=false) {
630
            if($pretty){
631
                $out = str_replace(CM_KEYMARKER,"»",$this->_key);
632
                if ($url && !strstr($out,'»')) {//provide no urls for plugins, etc.
633
                    if ($out == 'start') //one exception
634
                        return '<a href="http://www.dokuwiki.org/config:startpage">'.$out.'</a>';
635
                    else
636
                        return '<a href="http://www.dokuwiki.org/config:'.$out.'">'.$out.'</a>';
637
                }
638
                return $out;
639
            }else{
640
                return str_replace(CM_KEYMARKER,"']['",$this->_key);
641
            }
642
        }
643
    }
644
}
645
646
647
if (!class_exists('setting_array')) {
648
    /**
649
     * Class setting_array
650
     */
651
    class setting_array extends setting {
652
653
        /**
654
         * Create an array from a string
655
         *
656
         * @param string $string
657
         * @return array
658
         */
659
        protected function _from_string($string){
660
            $array = explode(',', $string);
661
            $array = array_map('trim', $array);
662
            $array = array_filter($array);
663
            $array = array_unique($array);
664
            return $array;
665
        }
666
667
        /**
668
         * Create a string from an array
669
         *
670
         * @param array $array
671
         * @return string
672
         */
673
        protected function _from_array($array){
674
            return join(', ', (array) $array);
675
        }
676
677
        /**
678
         * update setting with user provided value $input
679
         * if value fails error check, save it
680
         *
681
         * @param string $input
682
         * @return bool true if changed, false otherwise (incl. on error)
683
         */
684
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
685
            if (is_null($input)) return false;
686
            if ($this->is_protected()) return false;
687
688
            $input = $this->_from_string($input);
689
690
            $value = is_null($this->_local) ? $this->_default : $this->_local;
691
            if ($value == $input) return false;
692
693
            foreach($input as $item){
694
                if ($this->_pattern && !preg_match($this->_pattern,$item)) {
695
                    $this->_error = true;
696
                    $this->_input = $input;
697
                    return false;
698
                }
699
            }
700
701
            $this->_local = $input;
702
            return true;
703
        }
704
705
        /**
706
         * Escaping
707
         *
708
         * @param string $string
709
         * @return string
710
         */
711
        protected function _escape($string) {
712
            $tr = array("\\" => '\\\\', "'" => '\\\'');
713
            return "'".strtr( cleanText($string), $tr)."'";
714
        }
715
716
        /**
717
         * Generate string to save setting value to file according to $fmt
718
         *
719
         * @param string $var name of variable
720
         * @param string $fmt save format
721
         * @return string
722
         */
723
        function out($var, $fmt='php') {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
724
725
            if ($this->is_protected()) return '';
726
            if (is_null($this->_local) || ($this->_default == $this->_local)) return '';
727
728
            $out = '';
729
730
            if ($fmt=='php') {
731
                $vals = array_map(array($this, '_escape'), $this->_local);
732
                $out =  '$'.$var."['".$this->_out_key()."'] = array(".join(', ',$vals).");\n";
733
            }
734
735
            return $out;
736
        }
737
738
        /**
739
         * Build html for label and input of setting
740
         *
741
         * @param admin_plugin_config $plugin object of config plugin
742
         * @param bool            $echo   true: show inputted value, when error occurred, otherwise the stored setting
743
         * @return string[] with content array(string $label_html, string $input_html)
744
         */
745
        function html(admin_plugin_config $plugin, $echo=false) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
746
            $disable = '';
747
748
            if ($this->is_protected()) {
749
                $value = $this->_protected;
750
                $disable = 'disabled="disabled"';
751
            } else {
752
                if ($echo && $this->_error) {
753
                    $value = $this->_input;
754
                } else {
755
                    $value = is_null($this->_local) ? $this->_default : $this->_local;
756
                }
757
            }
758
759
            $key = htmlspecialchars($this->_key);
760
            $value = htmlspecialchars($this->_from_array($value));
761
762
            $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
763
            $input = '<input id="config___'.$key.'" name="config['.$key.']" type="text" class="edit" value="'.$value.'" '.$disable.'/>';
764
            return array($label,$input);
765
        }
766
    }
767
}
768
769
if (!class_exists('setting_string')) {
770
    /**
771
     * Class setting_string
772
     */
773
    class setting_string extends setting {
774
        /**
775
         * Build html for label and input of setting
776
         *
777
         * @param admin_plugin_config $plugin object of config plugin
778
         * @param bool            $echo   true: show inputted value, when error occurred, otherwise the stored setting
779
         * @return string[] with content array(string $label_html, string $input_html)
780
         */
781
        function html(admin_plugin_config $plugin, $echo=false) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
782
            $disable = '';
783
784
            if ($this->is_protected()) {
785
                $value = $this->_protected;
786
                $disable = 'disabled="disabled"';
787
            } else {
788
                if ($echo && $this->_error) {
789
                    $value = $this->_input;
790
                } else {
791
                    $value = is_null($this->_local) ? $this->_default : $this->_local;
792
                }
793
            }
794
795
            $key = htmlspecialchars($this->_key);
796
            $value = htmlspecialchars($value);
797
798
            $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
799
            $input = '<input id="config___'.$key.'" name="config['.$key.']" type="text" class="edit" value="'.$value.'" '.$disable.'/>';
800
            return array($label,$input);
801
        }
802
    }
803
}
804
805
if (!class_exists('setting_password')) {
806
    /**
807
     * Class setting_password
808
     */
809
    class setting_password extends setting_string {
810
811
        var $_code = 'plain';  // mechanism to be used to obscure passwords
812
813
        /**
814
         * update changed setting with user provided value $input
815
         * - if changed value fails error check, save it to $this->_input (to allow echoing later)
816
         * - if changed value passes error check, set $this->_local to the new value
817
         *
818
         * @param  mixed   $input   the new value
819
         * @return boolean          true if changed, false otherwise (also on error)
820
         */
821
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
822
            if ($this->is_protected()) return false;
823
            if (!$input) return false;
824
825
            if ($this->_pattern && !preg_match($this->_pattern,$input)) {
826
                $this->_error = true;
827
                $this->_input = $input;
828
                return false;
829
            }
830
831
            $this->_local = conf_encodeString($input,$this->_code);
832
            return true;
833
        }
834
835
        /**
836
         * Build html for label and input of setting
837
         *
838
         * @param admin_plugin_config $plugin object of config plugin
839
         * @param bool            $echo   true: show inputted value, when error occurred, otherwise the stored setting
840
         * @return string[] with content array(string $label_html, string $input_html)
841
         */
842
        function html(admin_plugin_config $plugin, $echo=false) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
843
844
            $disable = $this->is_protected() ? 'disabled="disabled"' : '';
845
846
            $key = htmlspecialchars($this->_key);
847
848
            $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
849
            $input = '<input id="config___'.$key.'" name="config['.$key.']" autocomplete="off" type="password" class="edit" value="" '.$disable.' />';
850
            return array($label,$input);
851
        }
852
    }
853
}
854
855
if (!class_exists('setting_email')) {
856
    /**
857
     * Class setting_email
858
     */
859
    class setting_email extends setting_string {
860
        var $_multiple = false;
861
        var $_placeholders = false;
862
863
        /**
864
         * update setting with user provided value $input
865
         * if value fails error check, save it
866
         *
867
         * @param mixed $input
868
         * @return boolean true if changed, false otherwise (incl. on error)
869
         */
870
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
871
            if (is_null($input)) return false;
872
            if ($this->is_protected()) return false;
873
874
            $value = is_null($this->_local) ? $this->_default : $this->_local;
875
            if ($value == $input) return false;
876
            if($input === ''){
877
                $this->_local = $input;
878
                return true;
879
            }
880
            $mail = $input;
881
882
            if($this->_placeholders){
883
                // replace variables with pseudo values
884
                $mail = str_replace('@USER@','joe',$mail);
885
                $mail = str_replace('@NAME@','Joe Schmoe',$mail);
886
                $mail = str_replace('@MAIL@','[email protected]',$mail);
887
            }
888
889
            // multiple mail addresses?
890
            if ($this->_multiple) {
891
                $mails = array_filter(array_map('trim', explode(',', $mail)));
892
            } else {
893
                $mails = array($mail);
894
            }
895
896
            // check them all
897
            foreach ($mails as $mail) {
898
                // only check the address part
899
                if(preg_match('#(.*?)<(.*?)>#', $mail, $matches)){
900
                    $addr = $matches[2];
901
                }else{
902
                    $addr = $mail;
903
                }
904
905
                if (!mail_isvalid($addr)) {
906
                    $this->_error = true;
907
                    $this->_input = $input;
908
                    return false;
909
                }
910
            }
911
912
            $this->_local = $input;
913
            return true;
914
        }
915
    }
916
}
917
918
if (!class_exists('setting_numeric')) {
919
    /**
920
     * Class setting_numeric
921
     */
922
    class setting_numeric extends setting_string {
923
        // This allows for many PHP syntax errors...
924
        // var $_pattern = '/^[-+\/*0-9 ]*$/';
925
        // much more restrictive, but should eliminate syntax errors.
926
        var $_pattern = '/^[-+]? *[0-9]+ *(?:[-+*] *[0-9]+ *)*$/';
927
        var $_min = null;
928
        var $_max = null;
929
930
        /**
931
         * update changed setting with user provided value $input
932
         * - if changed value fails error check, save it to $this->_input (to allow echoing later)
933
         * - if changed value passes error check, set $this->_local to the new value
934
         *
935
         * @param  mixed   $input   the new value
936
         * @return boolean          true if changed, false otherwise (also on error)
937
         */
938
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
939
            $local = $this->_local;
940
            $valid = parent::update($input);
941
            if ($valid && !(is_null($this->_min) && is_null($this->_max))) {
942
                $numeric_local = (int) eval('return '.$this->_local.';');
943
                if ((!is_null($this->_min) && $numeric_local < $this->_min) ||
944
                    (!is_null($this->_max) && $numeric_local > $this->_max)) {
945
                    $this->_error = true;
946
                    $this->_input = $input;
947
                    $this->_local = $local;
948
                    $valid = false;
949
                }
950
            }
951
            return $valid;
952
        }
953
954
        /**
955
         * Generate string to save setting value to file according to $fmt
956
         *
957
         * @param string $var name of variable
958
         * @param string $fmt save format
959
         * @return string
960
         */
961
        function out($var, $fmt='php') {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
962
963
            if ($this->is_protected()) return '';
964
            if (is_null($this->_local) || ($this->_default == $this->_local)) return '';
965
966
            $out = '';
967
968
            if ($fmt=='php') {
969
                $local = $this->_local === '' ? "''" : $this->_local;
970
                $out .=  '$'.$var."['".$this->_out_key()."'] = ".$local.";\n";
971
            }
972
973
            return $out;
974
        }
975
    }
976
}
977
978
if (!class_exists('setting_numericopt')) {
979
    /**
980
     * Class setting_numericopt
981
     */
982
    class setting_numericopt extends setting_numeric {
983
        // just allow an empty config
984
        var $_pattern = '/^(|[-]?[0-9]+(?:[-+*][0-9]+)*)$/';
985
986
987
        /**
988
         * Empty string is valid for numericopt
989
         *
990
         * @param mixed $input
991
         *
992
         * @return bool
993
         */
994
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
995
            if ($input === '') {
996
                return true;
997
            }
998
999
            return parent::update($input);
1000
        }
1001
    }
1002
}
1003
1004
if (!class_exists('setting_onoff')) {
1005
    /**
1006
     * Class setting_onoff
1007
     */
1008
    class setting_onoff extends setting_numeric {
1009
        /**
1010
         * Build html for label and input of setting
1011
         *
1012
         * @param admin_plugin_config $plugin object of config plugin
1013
         * @param bool            $echo   true: show inputted value, when error occurred, otherwise the stored setting
1014
         * @return string[] with content array(string $label_html, string $input_html)
1015
         */
1016
        function html(admin_plugin_config $plugin, $echo = false) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1017
            $disable = '';
1018
1019
            if ($this->is_protected()) {
1020
                $value = $this->_protected;
1021
                $disable = ' disabled="disabled"';
1022
            } else {
1023
                $value = is_null($this->_local) ? $this->_default : $this->_local;
1024
            }
1025
1026
            $key = htmlspecialchars($this->_key);
1027
            $checked = ($value) ? ' checked="checked"' : '';
1028
1029
            $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
1030
            $input = '<div class="input"><input id="config___'.$key.'" name="config['.$key.']" type="checkbox" class="checkbox" value="1"'.$checked.$disable.'/></div>';
1031
            return array($label,$input);
1032
        }
1033
1034
        /**
1035
         * update changed setting with user provided value $input
1036
         * - if changed value fails error check, save it to $this->_input (to allow echoing later)
1037
         * - if changed value passes error check, set $this->_local to the new value
1038
         *
1039
         * @param  mixed   $input   the new value
1040
         * @return boolean          true if changed, false otherwise (also on error)
1041
         */
1042
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1043
            if ($this->is_protected()) return false;
1044
1045
            $input = ($input) ? 1 : 0;
1046
            $value = is_null($this->_local) ? $this->_default : $this->_local;
1047
            if ($value == $input) return false;
1048
1049
            $this->_local = $input;
1050
            return true;
1051
        }
1052
    }
1053
}
1054
1055
if (!class_exists('setting_multichoice')) {
1056
    /**
1057
     * Class setting_multichoice
1058
     */
1059
    class setting_multichoice extends setting_string {
1060
        var $_choices = array();
1061
        var $lang; //some custom language strings are stored in setting
1062
1063
        /**
1064
         * Build html for label and input of setting
1065
         *
1066
         * @param admin_plugin_config $plugin object of config plugin
1067
         * @param bool            $echo   true: show inputted value, when error occurred, otherwise the stored setting
1068
         * @return string[] with content array(string $label_html, string $input_html)
1069
         */
1070
        function html(admin_plugin_config $plugin, $echo = false) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1071
            $disable = '';
1072
            $nochoice = '';
1073
1074
            if ($this->is_protected()) {
1075
                $value = $this->_protected;
1076
                $disable = ' disabled="disabled"';
1077
            } else {
1078
                $value = is_null($this->_local) ? $this->_default : $this->_local;
1079
            }
1080
1081
            // ensure current value is included
1082
            if (!in_array($value, $this->_choices)) {
1083
                $this->_choices[] = $value;
1084
            }
1085
            // disable if no other choices
1086
            if (!$this->is_protected() && count($this->_choices) <= 1) {
1087
                $disable = ' disabled="disabled"';
1088
                $nochoice = $plugin->getLang('nochoice');
1089
            }
1090
1091
            $key = htmlspecialchars($this->_key);
1092
1093
            $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
1094
1095
            $input = "<div class=\"input\">\n";
1096
            $input .= '<select class="edit" id="config___'.$key.'" name="config['.$key.']"'.$disable.'>'."\n";
1097
            foreach ($this->_choices as $choice) {
1098
                $selected = ($value == $choice) ? ' selected="selected"' : '';
1099
                $option = $plugin->getLang($this->_key.'_o_'.$choice);
1100
                if (!$option && isset($this->lang[$this->_key.'_o_'.$choice])) $option = $this->lang[$this->_key.'_o_'.$choice];
1101
                if (!$option) $option = $choice;
1102
1103
                $choice = htmlspecialchars($choice);
1104
                $option = htmlspecialchars($option);
1105
                $input .= '  <option value="'.$choice.'"'.$selected.' >'.$option.'</option>'."\n";
1106
            }
1107
            $input .= "</select> $nochoice \n";
1108
            $input .= "</div>\n";
1109
1110
            return array($label,$input);
1111
        }
1112
1113
        /**
1114
         * update changed setting with user provided value $input
1115
         * - if changed value fails error check, save it to $this->_input (to allow echoing later)
1116
         * - if changed value passes error check, set $this->_local to the new value
1117
         *
1118
         * @param  mixed   $input   the new value
1119
         * @return boolean          true if changed, false otherwise (also on error)
1120
         */
1121
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1122
            if (is_null($input)) return false;
1123
            if ($this->is_protected()) return false;
1124
1125
            $value = is_null($this->_local) ? $this->_default : $this->_local;
1126
            if ($value == $input) return false;
1127
1128
            if (!in_array($input, $this->_choices)) return false;
1129
1130
            $this->_local = $input;
1131
            return true;
1132
        }
1133
    }
1134
}
1135
1136
1137
if (!class_exists('setting_dirchoice')) {
1138
    /**
1139
     * Class setting_dirchoice
1140
     */
1141
    class setting_dirchoice extends setting_multichoice {
1142
1143
        var $_dir = '';
1144
1145
        /**
1146
         * Receives current values for the setting $key
1147
         *
1148
         * @param mixed $default   default setting value
1149
         * @param mixed $local     local setting value
1150
         * @param mixed $protected protected setting value
1151
         */
1152
        function initialize($default,$local,$protected) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1153
1154
            // populate $this->_choices with a list of directories
1155
            $list = array();
1156
1157
            if ($dh = @opendir($this->_dir)) {
1158
                while (false !== ($entry = readdir($dh))) {
1159
                    if ($entry == '.' || $entry == '..') continue;
1160
                    if ($this->_pattern && !preg_match($this->_pattern,$entry)) continue;
1161
1162
                    $file = (is_link($this->_dir.$entry)) ? readlink($this->_dir.$entry) : $this->_dir.$entry;
1163
                    if (is_dir($file)) $list[] = $entry;
1164
                }
1165
                closedir($dh);
1166
            }
1167
            sort($list);
1168
            $this->_choices = $list;
1169
1170
            parent::initialize($default,$local,$protected);
1171
        }
1172
    }
1173
}
1174
1175
1176
if (!class_exists('setting_hidden')) {
1177
    /**
1178
     * Class setting_hidden
1179
     */
1180
    class setting_hidden extends setting {
1181
        // Used to explicitly ignore a setting in the configuration manager.
1182
    }
1183
}
1184
1185
if (!class_exists('setting_fieldset')) {
1186
    /**
1187
     * Class setting_fieldset
1188
     */
1189
    class setting_fieldset extends setting {
1190
        // A do-nothing class used to detect the 'fieldset' type.
1191
        // Used to start a new settings "display-group".
1192
    }
1193
}
1194
1195
if (!class_exists('setting_undefined')) {
1196
    /**
1197
     * Class setting_undefined
1198
     */
1199
    class setting_undefined extends setting_hidden {
1200
        // A do-nothing class used to detect settings with no metadata entry.
1201
        // Used internaly to hide undefined settings, and generate the undefined settings list.
1202
    }
1203
}
1204
1205
if (!class_exists('setting_no_class')) {
1206
    /**
1207
     * Class setting_no_class
1208
     */
1209
    class setting_no_class extends setting_undefined {
1210
        // A do-nothing class used to detect settings with a missing setting class.
1211
        // Used internaly to hide undefined settings, and generate the undefined settings list.
1212
    }
1213
}
1214
1215
if (!class_exists('setting_no_default')) {
1216
    /**
1217
     * Class setting_no_default
1218
     */
1219
    class setting_no_default extends setting_undefined {
1220
        // A do-nothing class used to detect settings with no default value.
1221
        // Used internaly to hide undefined settings, and generate the undefined settings list.
1222
    }
1223
}
1224
1225
if (!class_exists('setting_multicheckbox')) {
1226
    /**
1227
     * Class setting_multicheckbox
1228
     */
1229
    class setting_multicheckbox extends setting_string {
1230
1231
        var $_choices = array();
1232
        var $_combine = array();
1233
        var $_other = 'always';
1234
1235
        /**
1236
         * update changed setting with user provided value $input
1237
         * - if changed value fails error check, save it to $this->_input (to allow echoing later)
1238
         * - if changed value passes error check, set $this->_local to the new value
1239
         *
1240
         * @param  mixed   $input   the new value
1241
         * @return boolean          true if changed, false otherwise (also on error)
1242
         */
1243
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1244
            if ($this->is_protected()) return false;
1245
1246
            // split any combined values + convert from array to comma separated string
1247
            $input = ($input) ? $input : array();
1248
            $input = $this->_array2str($input);
1249
1250
            $value = is_null($this->_local) ? $this->_default : $this->_local;
1251
            if ($value == $input) return false;
1252
1253
            if ($this->_pattern && !preg_match($this->_pattern,$input)) {
1254
                $this->_error = true;
1255
                $this->_input = $input;
1256
                return false;
1257
            }
1258
1259
            $this->_local = $input;
1260
            return true;
1261
        }
1262
1263
        /**
1264
         * Build html for label and input of setting
1265
         *
1266
         * @param admin_plugin_config $plugin object of config plugin
1267
         * @param bool            $echo   true: show input value, when error occurred, otherwise the stored setting
1268
         * @return string[] with content array(string $label_html, string $input_html)
1269
         */
1270
        function html(admin_plugin_config $plugin, $echo=false) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1271
1272
            $disable = '';
1273
1274
            if ($this->is_protected()) {
1275
                $value = $this->_protected;
1276
                $disable = 'disabled="disabled"';
1277
            } else {
1278
                if ($echo && $this->_error) {
1279
                    $value = $this->_input;
1280
                } else {
1281
                    $value = is_null($this->_local) ? $this->_default : $this->_local;
1282
                }
1283
            }
1284
1285
            $key = htmlspecialchars($this->_key);
1286
1287
            // convert from comma separated list into array + combine complimentary actions
1288
            $value = $this->_str2array($value);
1289
            $default = $this->_str2array($this->_default);
1290
1291
            $input = '';
1292
            foreach ($this->_choices as $choice) {
1293
                $idx = array_search($choice, $value);
1294
                $idx_default = array_search($choice,$default);
1295
1296
                $checked = ($idx !== false) ? 'checked="checked"' : '';
1297
1298
                // @todo ideally this would be handled using a second class of "default"
1299
                $class = (($idx !== false) == (false !== $idx_default)) ? " selectiondefault" : "";
1300
1301
                $prompt = ($plugin->getLang($this->_key.'_'.$choice) ?
1302
                                $plugin->getLang($this->_key.'_'.$choice) : htmlspecialchars($choice));
1303
1304
                $input .= '<div class="selection'.$class.'">'."\n";
1305
                $input .= '<label for="config___'.$key.'_'.$choice.'">'.$prompt."</label>\n";
1306
                $input .= '<input id="config___'.$key.'_'.$choice.'" name="config['.$key.'][]" type="checkbox" class="checkbox" value="'.$choice.'" '.$disable.' '.$checked."/>\n";
1307
                $input .= "</div>\n";
1308
1309
                // remove this action from the disabledactions array
1310
                if ($idx !== false) unset($value[$idx]);
1311
                if ($idx_default !== false) unset($default[$idx_default]);
1312
            }
1313
1314
            // handle any remaining values
1315
            if ($this->_other != 'never'){
1316
                $other = join(',',$value);
1317
                // test equivalent to ($this->_other == 'always' || ($other && $this->_other == 'exists')
1318
                // use != 'exists' rather than == 'always' to ensure invalid values default to 'always'
1319
                if ($this->_other != 'exists' || $other) {
1320
1321
                    $class = ((count($default) == count($value)) && (count($value) == count(array_intersect($value,$default)))) ?
1322
                                    " selectiondefault" : "";
1323
1324
                    $input .= '<div class="other'.$class.'">'."\n";
1325
                    $input .= '<label for="config___'.$key.'_other">'.$plugin->getLang($key.'_other')."</label>\n";
1326
                    $input .= '<input id="config___'.$key.'_other" name="config['.$key.'][other]" type="text" class="edit" value="'.htmlspecialchars($other).'" '.$disable." />\n";
1327
                    $input .= "</div>\n";
1328
                }
1329
            }
1330
            $label = '<label>'.$this->prompt($plugin).'</label>';
1331
            return array($label,$input);
1332
        }
1333
1334
        /**
1335
         * convert comma separated list to an array and combine any complimentary values
1336
         *
1337
         * @param string $str
1338
         * @return array
1339
         */
1340
        function _str2array($str) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1341
            $array = explode(',',$str);
1342
1343
            if (!empty($this->_combine)) {
1344
                foreach ($this->_combine as $key => $combinators) {
1345
                    $idx = array();
1346
                    foreach ($combinators as $val) {
1347
                        if  (($idx[] = array_search($val, $array)) === false) break;
1348
                    }
1349
1350
                    if (count($idx) && $idx[count($idx)-1] !== false) {
1351
                        foreach ($idx as $i) unset($array[$i]);
1352
                        $array[] = $key;
1353
                    }
1354
                }
1355
            }
1356
1357
            return $array;
1358
        }
1359
1360
        /**
1361
         * convert array of values + other back to a comma separated list, incl. splitting any combined values
1362
         *
1363
         * @param array $input
1364
         * @return string
1365
         */
1366
        function _array2str($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1367
1368
            // handle other
1369
            $other = trim($input['other']);
1370
            $other = !empty($other) ? explode(',',str_replace(' ','',$input['other'])) : array();
1371
            unset($input['other']);
1372
1373
            $array = array_unique(array_merge($input, $other));
1374
1375
            // deconstruct any combinations
1376
            if (!empty($this->_combine)) {
1377
                foreach ($this->_combine as $key => $combinators) {
1378
1379
                    $idx = array_search($key,$array);
1380
                    if ($idx !== false) {
1381
                        unset($array[$idx]);
1382
                        $array = array_merge($array, $combinators);
1383
                    }
1384
                }
1385
            }
1386
1387
            return join(',',array_unique($array));
1388
        }
1389
    }
1390
}
1391
1392
if (!class_exists('setting_regex')){
1393
    /**
1394
     * Class setting_regex
1395
     */
1396
    class setting_regex extends setting_string {
1397
1398
        var $_delimiter = '/';    // regex delimiter to be used in testing input
1399
        var $_pregflags = 'ui';   // regex pattern modifiers to be used in testing input
1400
1401
        /**
1402
         * update changed setting with user provided value $input
1403
         * - if changed value fails error check, save it to $this->_input (to allow echoing later)
1404
         * - if changed value passes error check, set $this->_local to the new value
1405
         *
1406
         * @param  mixed   $input   the new value
1407
         * @return boolean          true if changed, false otherwise (incl. on error)
1408
         */
1409
        function update($input) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1410
1411
            // let parent do basic checks, value, not changed, etc.
1412
            $local = $this->_local;
1413
            if (!parent::update($input)) return false;
1414
            $this->_local = $local;
1415
1416
            // see if the regex compiles and runs (we don't check for effectiveness)
1417
            $regex = $this->_delimiter . $input . $this->_delimiter . $this->_pregflags;
1418
            $lastError = error_get_last();
1419
            @preg_match($regex,'testdata');
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1420
            if (preg_last_error() != PREG_NO_ERROR || error_get_last() != $lastError) {
1421
                $this->_input = $input;
1422
                $this->_error = true;
1423
                return false;
1424
            }
1425
1426
            $this->_local = $input;
1427
            return true;
1428
        }
1429
    }
1430
}
1431