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