xref: /dokuwiki/lib/plugins/config/core/Configuration.php (revision e063babf89360f8f256f1595e5aa8be6a33b3dec)
1<?php
2/**
3 * Configuration Class
4 *
5 * @author  Chris Smith <chris@jalakai.co.uk>
6 * @author  Ben Coburn <btcoburn@silicodon.net>
7 */
8
9namespace dokuwiki\plugin\config\core;
10
11/**
12 * Class configuration
13 */
14class 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'];
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');
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);
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;
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);
317                @include(DOKU_PLUGIN . $plugin_dir . $class);
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);
334            @include(tpl_incdir() . $class);
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