Failed Conditions
Push — psr2-config ( c6639e )
by Andreas
06:39 queued 03:33
created

Configuration   C

Complexity

Total Complexity 71

Size/Duplication

Total Lines 438
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 438
rs 5.5904
c 0
b 0
f 0
wmc 71
lcom 1
cbo 3

14 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 23 5
C retrieve_settings() 0 57 12
D save_settings() 0 34 10
A touch_settings() 0 5 2
A _read_config_group() 0 8 2
C _read_config() 0 46 7
A _readValue() 0 19 3
A _out_header() 0 14 2
A _out_footer() 0 8 2
B _is_locked() 0 10 5
A _flatten() 0 16 3
A get_plugin_list() 0 14 2
D get_plugintpl_metadata() 0 41 10
B get_plugintpl_default() 0 24 6

How to fix   Complexity   

Complex Class

Complex classes like Configuration often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Configuration, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Configuration Class
4
 *
5
 * @author  Chris Smith <[email protected]>
6
 * @author  Ben Coburn <[email protected]>
7
 */
8
9
namespace dokuwiki\plugin\config\core;
10
11
/**
12
 * Class configuration
13
 */
14
class Configuration {
15
16
    const KEYMARKER = '____';
17
18
    protected $_name = 'conf';     // name of the config variable found in the files (overridden by $config['varname'])
19
    protected $_format = 'php';    // format of the config file, supported formats - php (overridden by $config['format'])
20
    protected $_heading = '';      // heading string written at top of config file - don't include comment indicators
21
    protected $_loaded = false;    // set to true after configuration files are loaded
22
    protected $_metadata = array();// holds metadata describing the settings
23
    /** @var Setting[] */
24
    public $setting = array();  // array of setting objects
25
    public $locked = false;     // configuration is considered locked if it can't be updated
26
    public $show_disabled_plugins = false;
27
28
    // configuration filenames
29
    protected $_default_files = array();
30
    protected $_local_files = array();      // updated configuration is written to the first file
31
    protected $_protected_files = array();
32
33
    protected $_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
        /** @var array $config gets loaded via include here */
49
        include($datafile);
50
51
        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...
52
        if(isset($config['format'])) $this->_format = $config['format'];
53
        if(isset($config['heading'])) $this->_heading = $config['heading'];
54
55
        $this->_default_files = $config_cascade['main']['default'];
56
        $this->_local_files = $config_cascade['main']['local'];
57
        $this->_protected_files = $config_cascade['main']['protected'];
58
59
        $this->locked = $this->_is_locked();
60
        $this->_metadata = array_merge($meta, $this->get_plugintpl_metadata($conf['template']));
61
        $this->retrieve_settings();
62
    }
63
64
    /**
65
     * Retrieve and stores settings in setting[] attribute
66
     */
67
    public function retrieve_settings() {
68
        global $conf;
69
        $no_default_check = array('setting_fieldset', 'setting_undefined', 'setting_no_class');
70
71
        if(!$this->_loaded) {
72
            $default = array_merge(
73
                $this->get_plugintpl_default($conf['template']),
74
                $this->_read_config_group($this->_default_files)
75
            );
76
            $local = $this->_read_config_group($this->_local_files);
77
            $protected = $this->_read_config_group($this->_protected_files);
78
79
            $keys = array_merge(
80
                array_keys($this->_metadata),
81
                array_keys($default),
82
                array_keys($local),
83
                array_keys($protected)
84
            );
85
            $keys = array_unique($keys);
86
87
            $param = null;
88
            foreach($keys as $key) {
89
                if(isset($this->_metadata[$key])) {
90
                    $class = $this->_metadata[$key][0];
91
92
                    if($class && class_exists('setting_' . $class)) {
93
                        $class = 'setting_' . $class;
94
                    } else {
95
                        if($class != '') {
96
                            $this->setting[] = new SettingNoClass($key, $param);
97
                        }
98
                        $class = 'setting';
99
                    }
100
101
                    $param = $this->_metadata[$key];
102
                    array_shift($param);
103
                } else {
104
                    $class = 'setting_undefined';
105
                    $param = null;
106
                }
107
108
                if(!in_array($class, $no_default_check) && !isset($default[$key])) {
109
                    $this->setting[] = new SettingNoDefault($key, $param);
110
                }
111
112
                $this->setting[$key] = new $class($key, $param);
113
114
                $d = array_key_exists($key, $default) ? $default[$key] : null;
115
                $l = array_key_exists($key, $local) ? $local[$key] : null;
116
                $p = array_key_exists($key, $protected) ? $protected[$key] : null;
117
118
                $this->setting[$key]->initialize($d, $l, $p);
119
            }
120
121
            $this->_loaded = true;
122
        }
123
    }
124
125
    /**
126
     * Stores setting[] array to file
127
     *
128
     * @param string $id Name of plugin, which saves the settings
129
     * @param string $header Text at the top of the rewritten settings file
130
     * @param bool $backup backup current file? (remove any existing backup)
131
     * @return bool succesful?
132
     */
133
    public function save_settings($id, $header = '', $backup = true) {
134
        global $conf;
135
136
        if($this->locked) return false;
137
138
        // write back to the last file in the local config cascade
139
        $file = end($this->_local_files);
140
141
        // backup current file (remove any existing backup)
142
        if(file_exists($file) && $backup) {
143
            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...
144
            if(!io_rename($file, $file . '.bak')) return false;
145
        }
146
147
        if(!$fh = @fopen($file, 'wb')) {
148
            io_rename($file . '.bak', $file);     // problem opening, restore the backup
149
            return false;
150
        }
151
152
        if(empty($header)) $header = $this->_heading;
153
154
        $out = $this->_out_header($id, $header);
155
156
        foreach($this->setting as $setting) {
157
            $out .= $setting->out($this->_name, $this->_format);
158
        }
159
160
        $out .= $this->_out_footer();
161
162
        @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...
163
        fclose($fh);
164
        if($conf['fperm']) chmod($file, $conf['fperm']);
165
        return true;
166
    }
167
168
    /**
169
     * Update last modified time stamp of the config file
170
     *
171
     * @return bool
172
     */
173
    public function touch_settings() {
174
        if($this->locked) return false;
175
        $file = end($this->_local_files);
176
        return @touch($file);
177
    }
178
179
    /**
180
     * Read and merge given config files
181
     *
182
     * @param array $files file paths
183
     * @return array config settings
184
     */
185
    protected function _read_config_group($files) {
186
        $config = array();
187
        foreach($files as $file) {
188
            $config = array_merge($config, $this->_read_config($file));
189
        }
190
191
        return $config;
192
    }
193
194
    /**
195
     * Return an array of config settings
196
     *
197
     * @param string $file file path
198
     * @return array config settings
199
     */
200
    protected function _read_config($file) {
201
202
        if(!$file) return array();
203
204
        $config = array();
205
206
        if($this->_format == 'php') {
207
208
            if(file_exists($file)) {
209
                $contents = @php_strip_whitespace($file);
210
            } else {
211
                $contents = '';
212
            }
213
            $pattern = '/\$' . $this->_name . '\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);(?=[^;]*(?:\$' . $this->_name . '|$))/s';
214
            $matches = array();
215
            preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER);
216
217
            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...
218
                $value = $matches[$i][2];
219
220
                // correct issues with the incoming data
221
                // FIXME ... for now merge multi-dimensional array indices using ____
222
                $key = preg_replace('/.\]\[./', Configuration::KEYMARKER, $matches[$i][1]);
223
224
                // handle arrays
225
                if(preg_match('/^array ?\((.*)\)/', $value, $match)) {
226
                    $arr = explode(',', $match[1]);
227
228
                    // remove quotes from quoted strings & unescape escaped data
229
                    $len = count($arr);
230
                    for($j = 0; $j < $len; $j++) {
231
                        $arr[$j] = trim($arr[$j]);
232
                        $arr[$j] = $this->_readValue($arr[$j]);
233
                    }
234
235
                    $value = $arr;
236
                } else {
237
                    $value = $this->_readValue($value);
238
                }
239
240
                $config[$key] = $value;
241
            }
242
        }
243
244
        return $config;
245
    }
246
247
    /**
248
     * Convert php string into value
249
     *
250
     * @param string $value
251
     * @return bool|string
252
     */
253
    protected function _readValue($value) {
254
        $removequotes_pattern = '/^(\'|")(.*)(?<!\\\\)\1$/s';
255
        $unescape_pairs = array(
256
            '\\\\' => '\\',
257
            '\\\'' => '\'',
258
            '\\"' => '"'
259
        );
260
261
        if($value == 'true') {
262
            $value = true;
263
        } elseif($value == 'false') {
264
            $value = false;
265
        } else {
266
            // remove quotes from quoted strings & unescape escaped data
267
            $value = preg_replace($removequotes_pattern, '$2', $value);
268
            $value = strtr($value, $unescape_pairs);
269
        }
270
        return $value;
271
    }
272
273
    /**
274
     * Returns header of rewritten settings file
275
     *
276
     * @param string $id plugin name of which generated this output
277
     * @param string $header additional text for at top of the file
278
     * @return string text of header
279
     */
280
    protected function _out_header($id, $header) {
281
        $out = '';
282
        if($this->_format == 'php') {
283
            $out .= '<' . '?php' . "\n" .
284
                "/*\n" .
285
                " * " . $header . "\n" .
286
                " * Auto-generated by " . $id . " plugin\n" .
287
                " * Run for user: " . $_SERVER['REMOTE_USER'] . "\n" .
288
                " * Date: " . date('r') . "\n" .
289
                " */\n\n";
290
        }
291
292
        return $out;
293
    }
294
295
    /**
296
     * Returns footer of rewritten settings file
297
     *
298
     * @return string text of footer
299
     */
300
    protected function _out_footer() {
301
        $out = '';
302
        if($this->_format == 'php') {
303
            $out .= "\n// end auto-generated content\n";
304
        }
305
306
        return $out;
307
    }
308
309
    /**
310
     * Configuration is considered locked if there is no local settings filename
311
     * or the directory its in is not writable or the file exists and is not writable
312
     *
313
     * @return bool true: locked, false: writable
314
     */
315
    protected function _is_locked() {
316
        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...
317
318
        $local = $this->_local_files[0];
319
320
        if(!is_writable(dirname($local))) return true;
321
        if(file_exists($local) && !is_writable($local)) return true;
322
323
        return false;
324
    }
325
326
    /**
327
     * not used ... conf's contents are an array!
328
     * reduce any multidimensional settings to one dimension using Configuration::KEYMARKER
329
     *
330
     * @param $conf
331
     * @param string $prefix
332
     * @return array
333
     */
334
    protected function _flatten($conf, $prefix = '') {
335
336
        $out = array();
337
338
        foreach($conf as $key => $value) {
339
            if(!is_array($value)) {
340
                $out[$prefix . $key] = $value;
341
                continue;
342
            }
343
344
            $tmp = $this->_flatten($value, $prefix . $key . Configuration::KEYMARKER);
345
            $out = array_merge($out, $tmp);
346
        }
347
348
        return $out;
349
    }
350
351
    /**
352
     * Returns array of plugin names
353
     *
354
     * @return array plugin names
355
     * @triggers PLUGIN_CONFIG_PLUGINLIST event
356
     */
357
    protected function get_plugin_list() {
358
        if(is_null($this->_plugin_list)) {
359
            $list = plugin_list('', $this->show_disabled_plugins);
360
361
            // remove this plugin from the list
362
            $idx = array_search('config', $list);
363
            unset($list[$idx]);
364
365
            trigger_event('PLUGIN_CONFIG_PLUGINLIST', $list);
366
            $this->_plugin_list = $list;
367
        }
368
369
        return $this->_plugin_list;
370
    }
371
372
    /**
373
     * load metadata for plugin and template settings
374
     *
375
     * @param string $tpl name of active template
376
     * @return array metadata of settings
377
     */
378
    protected function get_plugintpl_metadata($tpl) {
379
        $file = '/conf/metadata.php';
380
        $class = '/conf/settings.class.php';
381
        $metadata = array();
382
383
        foreach($this->get_plugin_list() as $plugin) {
384
            $plugin_dir = plugin_directory($plugin);
385
            if(file_exists(DOKU_PLUGIN . $plugin_dir . $file)) {
386
                $meta = array();
387
                @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...
388
                @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...
389
                if(!empty($meta)) {
390
                    $metadata['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . 'plugin_settings_name'] = ['fieldset'];
391
                }
392
                foreach($meta as $key => $value) {
393
                    if($value[0] == 'fieldset') {
394
                        continue;
395
                    } //plugins only get one fieldset
396
                    $metadata['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . $key] = $value;
397
                }
398
            }
399
        }
400
401
        // the same for the active template
402
        if(file_exists(tpl_incdir() . $file)) {
403
            $meta = array();
404
            @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...
405
            @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...
406
            if(!empty($meta)) {
407
                $metadata['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . 'template_settings_name'] = array('fieldset');
408
            }
409
            foreach($meta as $key => $value) {
410
                if($value[0] == 'fieldset') {
411
                    continue;
412
                } //template only gets one fieldset
413
                $metadata['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . $key] = $value;
414
            }
415
        }
416
417
        return $metadata;
418
    }
419
420
    /**
421
     * Load default settings for plugins and templates
422
     *
423
     * @param string $tpl name of active template
424
     * @return array default settings
425
     */
426
    protected function get_plugintpl_default($tpl) {
427
        $file = '/conf/default.php';
428
        $default = array();
429
430
        foreach($this->get_plugin_list() as $plugin) {
431
            $plugin_dir = plugin_directory($plugin);
432
            if(file_exists(DOKU_PLUGIN . $plugin_dir . $file)) {
433
                $conf = $this->_read_config(DOKU_PLUGIN . $plugin_dir . $file);
434
                foreach($conf as $key => $value) {
435
                    $default['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . $key] = $value;
436
                }
437
            }
438
        }
439
440
        // the same for the active template
441
        if(file_exists(tpl_incdir() . $file)) {
442
            $conf = $this->_read_config(tpl_incdir() . $file);
443
            foreach($conf as $key => $value) {
444
                $default['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . $key] = $value;
445
            }
446
        }
447
448
        return $default;
449
    }
450
451
}
452
453