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