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