1<?php
2
3namespace dokuwiki\plugin\config\core;
4
5use dokuwiki\plugin\config\core\Setting\Setting;
6use dokuwiki\plugin\config\core\Setting\SettingNoClass;
7use dokuwiki\plugin\config\core\Setting\SettingNoDefault;
8use dokuwiki\plugin\config\core\Setting\SettingNoKnownClass;
9use dokuwiki\plugin\config\core\Setting\SettingUndefined;
10
11/**
12 * Holds all the current settings and proxies the Loader and Writer
13 *
14 * @author Chris Smith <chris@jalakai.co.uk>
15 * @author Ben Coburn <btcoburn@silicodon.net>
16 * @author Andreas Gohr <andi@splitbrain.org>
17 */
18class Configuration {
19
20    const KEYMARKER = '____';
21
22    /** @var Setting[] metadata as array of Settings objects */
23    protected $settings = array();
24    /** @var Setting[] undefined and problematic settings */
25    protected $undefined = array();
26
27    /** @var array all metadata */
28    protected $metadata;
29    /** @var array all default settings */
30    protected $default;
31    /** @var array all local settings */
32    protected $local;
33    /** @var array all protected settings */
34    protected $protected;
35
36    /** @var bool have the settings been changed since loading from disk? */
37    protected $changed = false;
38
39    /** @var Loader */
40    protected $loader;
41    /** @var Writer */
42    protected $writer;
43
44    /**
45     * ConfigSettings constructor.
46     */
47    public function __construct() {
48        $this->loader = new Loader(new ConfigParser());
49        $this->writer = new Writer();
50
51        $this->metadata = $this->loader->loadMeta();
52        $this->default = $this->loader->loadDefaults();
53        $this->local = $this->loader->loadLocal();
54        $this->protected = $this->loader->loadProtected();
55
56        $this->initSettings();
57    }
58
59    /**
60     * Get all settings
61     *
62     * @return Setting[]
63     */
64    public function getSettings() {
65        return $this->settings;
66    }
67
68    /**
69     * Get all unknown or problematic settings
70     *
71     * @return Setting[]
72     */
73    public function getUndefined() {
74        return $this->undefined;
75    }
76
77    /**
78     * Have the settings been changed since loading from disk?
79     *
80     * @return bool
81     */
82    public function hasChanged() {
83        return $this->changed;
84    }
85
86    /**
87     * Check if the config can be written
88     *
89     * @return bool
90     */
91    public function isLocked() {
92        return $this->writer->isLocked();
93    }
94
95    /**
96     * Update the settings using the data provided
97     *
98     * @param array $input as posted
99     * @return bool true if all updates went through, false on errors
100     */
101    public function updateSettings($input) {
102        $ok = true;
103
104        foreach($this->settings as $key => $obj) {
105            $value = isset($input[$key]) ? $input[$key] : null;
106            if($obj->update($value)) {
107                $this->changed = true;
108            }
109            if($obj->hasError()) $ok = false;
110        }
111
112        return $ok;
113    }
114
115    /**
116     * Save the settings
117     *
118     * This save the current state as defined in this object, including the
119     * undefined settings
120     *
121     * @throws \Exception
122     */
123    public function save() {
124        // only save the undefined settings that have not been handled in settings
125        $undefined = array_diff_key($this->undefined, $this->settings);
126        $this->writer->save(array_merge($this->settings, $undefined));
127    }
128
129    /**
130     * Touch the settings
131     *
132     * @throws \Exception
133     */
134    public function touch() {
135        $this->writer->touch();
136    }
137
138    /**
139     * Load the extension language strings
140     *
141     * @return array
142     */
143    public function getLangs() {
144        return $this->loader->loadLangs();
145    }
146
147    /**
148     * Initalizes the $settings and $undefined properties
149     */
150    protected function initSettings() {
151        $keys = array_merge(
152            array_keys($this->metadata),
153            array_keys($this->default),
154            array_keys($this->local),
155            array_keys($this->protected)
156        );
157        $keys = array_unique($keys);
158
159        foreach($keys as $key) {
160            $obj = $this->instantiateClass($key);
161
162            if($obj->shouldHaveDefault() && !isset($this->default[$key])) {
163                $this->undefined[$key] = new SettingNoDefault($key);
164            }
165
166            $d = isset($this->default[$key]) ? $this->default[$key] : null;
167            $l = isset($this->local[$key]) ? $this->local[$key] : null;
168            $p = isset($this->protected[$key]) ? $this->protected[$key] : null;
169
170            $obj->initialize($d, $l, $p);
171        }
172    }
173
174    /**
175     * Instantiates the proper class for the given config key
176     *
177     * The class is added to the $settings or $undefined arrays and returned
178     *
179     * @param string $key
180     * @return Setting
181     */
182    protected function instantiateClass($key) {
183        if(isset($this->metadata[$key])) {
184            $param = $this->metadata[$key];
185            $class = $this->determineClassName(array_shift($param), $key); // first param is class
186            $obj = new $class($key, $param);
187            $this->settings[$key] = $obj;
188        } else {
189            $obj = new SettingUndefined($key);
190            $this->undefined[$key] = $obj;
191        }
192        return $obj;
193    }
194
195    /**
196     * Return the class to load
197     *
198     * @param string $class the class name as given in the meta file
199     * @param string $key the settings key
200     * @return string
201     */
202    protected function determineClassName($class, $key) {
203        // try namespaced class first
204        if(is_string($class)) {
205            $modern = str_replace('_', '', ucwords($class, '_'));
206            $modern = '\\dokuwiki\\plugin\\config\\core\\Setting\\Setting' . $modern;
207            if($modern && class_exists($modern)) return $modern;
208            // try class as given
209            if(class_exists($class)) return $class;
210            // class wasn't found add to errors
211            $this->undefined[$key] = new SettingNoKnownClass($key);
212        } else {
213            // no class given, add to errors
214            $this->undefined[$key] = new SettingNoClass($key);
215        }
216        return '\\dokuwiki\\plugin\\config\\core\\Setting\\Setting';
217    }
218
219}
220