Completed
Push — psr2-config ( c6639e...e063ba )
by Andreas
03:24
created

Configuration::_readValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 14
nc 3
nop 1
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
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
22
    /** @var ConfigParser */
23
    protected $parser;
24
25
26
    protected $_loaded = false;    // set to true after configuration files are loaded
27
    protected $_metadata = array();// holds metadata describing the settings
28
    /** @var Setting[] */
29
    public $setting = array();  // array of setting objects
30
    public $locked = false;     // configuration is considered locked if it can't be updated
31
    public $show_disabled_plugins = false;
32
33
    // configuration filenames
34
    protected $_default_files = array();
35
    protected $_local_files = array();      // updated configuration is written to the first file
36
    protected $_protected_files = array();
37
38
    protected $_plugin_list = null;
39
40
    /**
41
     * constructor
42
     *
43
     * @param string $datafile path to config metadata file
44
     */
45
    public function __construct($datafile) {
46
        global $conf, $config_cascade;
47
48
        if(!file_exists($datafile)) {
49
            msg('No configuration metadata found at - ' . htmlspecialchars($datafile), -1);
50
            return;
51
        }
52
        $meta = array();
53
        /** @var array $config gets loaded via include here */
54
        include($datafile);
55
56
        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...
57
        if(isset($config['format'])) $this->_format = $config['format'];
58
        if(isset($config['heading'])) $this->_heading = $config['heading'];
59
60
        $this->_default_files = $config_cascade['main']['default'];
61
        $this->_local_files = $config_cascade['main']['local'];
62
        $this->_protected_files = $config_cascade['main']['protected'];
63
64
        $this->parser = new ConfigParser($this->_name, Configuration::KEYMARKER);
65
66
        $this->locked = $this->_is_locked();
67
        $this->_metadata = array_merge($meta, $this->get_plugintpl_metadata($conf['template']));
68
        $this->retrieve_settings();
69
    }
70
71
    /**
72
     * Retrieve and stores settings in setting[] attribute
73
     */
74
    public function retrieve_settings() {
75
        global $conf;
76
        $no_default_check = array('setting_fieldset', 'setting_undefined', 'setting_no_class');
77
78
        if(!$this->_loaded) {
79
            $default = array_merge(
80
                $this->get_plugintpl_default($conf['template']),
81
                $this->_read_config_group($this->_default_files)
82
            );
83
            $local = $this->_read_config_group($this->_local_files);
84
            $protected = $this->_read_config_group($this->_protected_files);
85
86
            $keys = array_merge(
87
                array_keys($this->_metadata),
88
                array_keys($default),
89
                array_keys($local),
90
                array_keys($protected)
91
            );
92
            $keys = array_unique($keys);
93
94
            $param = null;
95
            foreach($keys as $key) {
96
                if(isset($this->_metadata[$key])) {
97
                    $class = $this->_metadata[$key][0];
98
99
                    if($class && class_exists('setting_' . $class)) {
100
                        $class = 'setting_' . $class;
101
                    } else {
102
                        if($class != '') {
103
                            $this->setting[] = new SettingNoClass($key, $param);
104
                        }
105
                        $class = 'setting';
106
                    }
107
108
                    $param = $this->_metadata[$key];
109
                    array_shift($param);
110
                } else {
111
                    $class = 'setting_undefined';
112
                    $param = null;
113
                }
114
115
                if(!in_array($class, $no_default_check) && !isset($default[$key])) {
116
                    $this->setting[] = new SettingNoDefault($key, $param);
117
                }
118
119
                $this->setting[$key] = new $class($key, $param);
120
121
                $d = array_key_exists($key, $default) ? $default[$key] : null;
122
                $l = array_key_exists($key, $local) ? $local[$key] : null;
123
                $p = array_key_exists($key, $protected) ? $protected[$key] : null;
124
125
                $this->setting[$key]->initialize($d, $l, $p);
126
            }
127
128
            $this->_loaded = true;
129
        }
130
    }
131
132
    /**
133
     * Stores setting[] array to file
134
     *
135
     * @param string $id Name of plugin, which saves the settings
136
     * @param string $header Text at the top of the rewritten settings file
137
     * @param bool $backup backup current file? (remove any existing backup)
138
     * @return bool succesful?
139
     */
140
    public function save_settings($id, $header = '', $backup = true) {
141
        global $conf;
142
143
        if($this->locked) return false;
144
145
        // write back to the last file in the local config cascade
146
        $file = end($this->_local_files);
147
148
        // backup current file (remove any existing backup)
149
        if(file_exists($file) && $backup) {
150
            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...
151
            if(!io_rename($file, $file . '.bak')) return false;
152
        }
153
154
        if(!$fh = @fopen($file, 'wb')) {
155
            io_rename($file . '.bak', $file);     // problem opening, restore the backup
156
            return false;
157
        }
158
159
        if(empty($header)) $header = $this->_heading;
160
161
        $out = $this->_out_header($id, $header);
162
163
        foreach($this->setting as $setting) {
164
            $out .= $setting->out($this->_name, $this->_format);
165
        }
166
167
        $out .= $this->_out_footer();
168
169
        @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...
170
        fclose($fh);
171
        if($conf['fperm']) chmod($file, $conf['fperm']);
172
        return true;
173
    }
174
175
    /**
176
     * Update last modified time stamp of the config file
177
     *
178
     * @return bool
179
     */
180
    public function touch_settings() {
181
        if($this->locked) return false;
182
        $file = end($this->_local_files);
183
        return @touch($file);
184
    }
185
186
    /**
187
     * Read and merge given config files
188
     *
189
     * @param array $files file paths
190
     * @return array config settings
191
     */
192
    protected function _read_config_group($files) {
193
        $config = array();
194
        foreach($files as $file) {
195
            $config = array_merge($config, $this->parser->parse($file));
196
        }
197
198
        return $config;
199
    }
200
201
202
    /**
203
     * Returns header of rewritten settings file
204
     *
205
     * @param string $id plugin name of which generated this output
206
     * @param string $header additional text for at top of the file
207
     * @return string text of header
208
     */
209
    protected function _out_header($id, $header) {
210
        $out = '';
211
        if($this->_format == 'php') {
212
            $out .= '<' . '?php' . "\n" .
213
                "/*\n" .
214
                " * " . $header . "\n" .
215
                " * Auto-generated by " . $id . " plugin\n" .
216
                " * Run for user: " . $_SERVER['REMOTE_USER'] . "\n" .
217
                " * Date: " . date('r') . "\n" .
218
                " */\n\n";
219
        }
220
221
        return $out;
222
    }
223
224
    /**
225
     * Returns footer of rewritten settings file
226
     *
227
     * @return string text of footer
228
     */
229
    protected function _out_footer() {
230
        $out = '';
231
        if($this->_format == 'php') {
232
            $out .= "\n// end auto-generated content\n";
233
        }
234
235
        return $out;
236
    }
237
238
    /**
239
     * Configuration is considered locked if there is no local settings filename
240
     * or the directory its in is not writable or the file exists and is not writable
241
     *
242
     * @return bool true: locked, false: writable
243
     */
244
    protected function _is_locked() {
245
        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...
246
247
        $local = $this->_local_files[0];
248
249
        if(!is_writable(dirname($local))) return true;
250
        if(file_exists($local) && !is_writable($local)) return true;
251
252
        return false;
253
    }
254
255
    /**
256
     * not used ... conf's contents are an array!
257
     * reduce any multidimensional settings to one dimension using Configuration::KEYMARKER
258
     *
259
     * @param $conf
260
     * @param string $prefix
261
     * @return array
262
     */
263
    protected function _flatten($conf, $prefix = '') {
264
265
        $out = array();
266
267
        foreach($conf as $key => $value) {
268
            if(!is_array($value)) {
269
                $out[$prefix . $key] = $value;
270
                continue;
271
            }
272
273
            $tmp = $this->_flatten($value, $prefix . $key . Configuration::KEYMARKER);
274
            $out = array_merge($out, $tmp);
275
        }
276
277
        return $out;
278
    }
279
280
    /**
281
     * Returns array of plugin names
282
     *
283
     * @return array plugin names
284
     * @triggers PLUGIN_CONFIG_PLUGINLIST event
285
     */
286
    protected function get_plugin_list() {
287
        if(is_null($this->_plugin_list)) {
288
            $list = plugin_list('', $this->show_disabled_plugins);
289
290
            // remove this plugin from the list
291
            $idx = array_search('config', $list);
292
            unset($list[$idx]);
293
294
            trigger_event('PLUGIN_CONFIG_PLUGINLIST', $list);
295
            $this->_plugin_list = $list;
296
        }
297
298
        return $this->_plugin_list;
299
    }
300
301
    /**
302
     * load metadata for plugin and template settings
303
     *
304
     * @param string $tpl name of active template
305
     * @return array metadata of settings
306
     */
307
    protected function get_plugintpl_metadata($tpl) {
308
        $file = '/conf/metadata.php';
309
        $class = '/conf/settings.class.php';
310
        $metadata = array();
311
312
        foreach($this->get_plugin_list() as $plugin) {
313
            $plugin_dir = plugin_directory($plugin);
314
            if(file_exists(DOKU_PLUGIN . $plugin_dir . $file)) {
315
                $meta = array();
316
                @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...
317
                @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...
318
                if(!empty($meta)) {
319
                    $metadata['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . 'plugin_settings_name'] = ['fieldset'];
320
                }
321
                foreach($meta as $key => $value) {
322
                    if($value[0] == 'fieldset') {
323
                        continue;
324
                    } //plugins only get one fieldset
325
                    $metadata['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . $key] = $value;
326
                }
327
            }
328
        }
329
330
        // the same for the active template
331
        if(file_exists(tpl_incdir() . $file)) {
332
            $meta = array();
333
            @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...
334
            @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...
335
            if(!empty($meta)) {
336
                $metadata['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . 'template_settings_name'] = array('fieldset');
337
            }
338
            foreach($meta as $key => $value) {
339
                if($value[0] == 'fieldset') {
340
                    continue;
341
                } //template only gets one fieldset
342
                $metadata['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . $key] = $value;
343
            }
344
        }
345
346
        return $metadata;
347
    }
348
349
    /**
350
     * Load default settings for plugins and templates
351
     *
352
     * @param string $tpl name of active template
353
     * @return array default settings
354
     */
355
    protected function get_plugintpl_default($tpl) {
356
        $file = '/conf/default.php';
357
        $default = array();
358
359
        foreach($this->get_plugin_list() as $plugin) {
360
            $plugin_dir = plugin_directory($plugin);
361
            if(file_exists(DOKU_PLUGIN . $plugin_dir . $file)) {
362
                $conf = $this->parser->parse(DOKU_PLUGIN . $plugin_dir . $file);
363
                foreach($conf as $key => $value) {
364
                    $default['plugin' . Configuration::KEYMARKER . $plugin . Configuration::KEYMARKER . $key] = $value;
365
                }
366
            }
367
        }
368
369
        // the same for the active template
370
        if(file_exists(tpl_incdir() . $file)) {
371
            $conf = $this->parser->parse(tpl_incdir() . $file);
372
            foreach($conf as $key => $value) {
373
                $default['tpl' . Configuration::KEYMARKER . $tpl . Configuration::KEYMARKER . $key] = $value;
374
            }
375
        }
376
377
        return $default;
378
    }
379
380
}
381
382