1 <?php
2
3 4 5 6 7 8 9 10
11 class rex_config
12 {
13 14 15 16 17
18 private static $initialized = false;
19
20 21 22 23 24
25 private static $cacheFile;
26
27 28 29 30 31
32 private static $changed = false;
33
34 35 36 37 38
39 private static $data = [];
40
41 42 43 44 45
46 private static $changedData = [];
47
48 49 50 51 52
53 private static $deletedData = [];
54
55 56 57 58 59 60 61 62 63 64 65 66 67 68
69 public static function set($namespace, $key, $value = null)
70 {
71 self::init();
72
73 if (!is_string($namespace)) {
74 throw new InvalidArgumentException('rex_config: expecting $namespace to be a string, ' . gettype($namespace) . ' given!');
75 }
76
77 if (is_array($key)) {
78 $existed = false;
79 foreach ($key as $k => $v) {
80 $existed = self::set($namespace, $k, $v) || $existed;
81 }
82 return $existed;
83 }
84
85 if (!is_string($key)) {
86 throw new InvalidArgumentException('rex_config: expecting $key to be a string, ' . gettype($key) . ' given!');
87 }
88
89 if (!isset(self::$data[$namespace])) {
90 self::$data[$namespace] = [];
91 }
92
93 $existed = isset(self::$data[$namespace][$key]);
94 if (!$existed || $existed && self::$data[$namespace][$key] !== $value) {
95
96 self::$changedData[$namespace][$key] = $value;
97
98
99 unset(self::$deletedData[$namespace][$key]);
100
101
102 self::$data[$namespace][$key] = $value;
103 self::$changed = true;
104 }
105
106 return $existed;
107 }
108
109 110 111 112 113 114 115 116 117 118 119 120 121 122
123 public static function get($namespace, $key = null, $default = null)
124 {
125 self::init();
126
127 if (!is_string($namespace)) {
128 throw new InvalidArgumentException('rex_config: expecting $namespace to be a string, ' . gettype($namespace) . ' given!');
129 }
130
131 if ($key === null) {
132 return isset(self::$data[$namespace]) ? self::$data[$namespace] : [];
133 }
134
135 if (!is_string($key)) {
136 throw new InvalidArgumentException('rex_config: expecting $key to be a string, ' . gettype($key) . ' given!');
137 }
138
139 if (isset(self::$data[$namespace][$key])) {
140 return self::$data[$namespace][$key];
141 }
142 return $default;
143 }
144
145 146 147 148 149 150 151 152 153 154
155 public static function has($namespace, $key = null)
156 {
157 self::init();
158
159 if (!is_string($namespace)) {
160 throw new InvalidArgumentException('rex_config: expecting $namespace to be a string, ' . gettype($namespace) . ' given!');
161 }
162
163 if ($key === null) {
164 return isset(self::$data[$namespace]);
165 }
166
167 if (!is_string($key)) {
168 throw new InvalidArgumentException('rex_config: expecting $key to be a string, ' . gettype($key) . ' given!');
169 }
170
171 return isset(self::$data[$namespace][$key]);
172 }
173
174 175 176 177 178 179 180 181 182 183
184 public static function remove($namespace, $key)
185 {
186 self::init();
187
188 if (!is_string($namespace)) {
189 throw new InvalidArgumentException('rex_config: expecting $namespace to be a string, ' . gettype($namespace) . ' given!');
190 }
191 if (!is_string($key)) {
192 throw new InvalidArgumentException('rex_config: expecting $key to be a string, ' . gettype($key) . ' given!');
193 }
194
195 if (isset(self::$data[$namespace][$key])) {
196
197 self::$deletedData[$namespace][$key] = true;
198
199
200 unset(self::$changedData[$namespace][$key]);
201
202
203 unset(self::$data[$namespace][$key]);
204 if (empty(self::$data[$namespace])) {
205 unset(self::$data[$namespace]);
206 }
207 self::$changed = true;
208 return true;
209 }
210 return false;
211 }
212
213 214 215 216 217 218 219 220 221
222 public static function removeNamespace($namespace)
223 {
224 self::init();
225
226 if (!is_string($namespace)) {
227 throw new InvalidArgumentException('rex_config: expecting $namespace to be a string, ' . gettype($namespace) . ' given!');
228 }
229
230 if (isset(self::$data[$namespace])) {
231 foreach (self::$data[$namespace] as $key => $value) {
232 self::remove($namespace, $key);
233 }
234
235 unset(self::$data[$namespace]);
236 self::$changed = true;
237 return true;
238 }
239 return false;
240 }
241
242 243 244
245 public static function refresh()
246 {
247 if (!self::$initialized) {
248 self::init();
249
250 return;
251 }
252
253 self::loadFromDb();
254
255 self::generateCache();
256
257 self::$changed = false;
258 self::$changedData = [];
259 self::$deletedData = [];
260 }
261
262 263 264
265 protected static function init()
266 {
267 if (self::$initialized) {
268 return;
269 }
270
271 self::$cacheFile = rex_path::coreCache('config.cache');
272
273
274
275 $dir = dirname(self::$cacheFile);
276 rex_dir::create($dir);
277 if (!is_writable($dir)) {
278 throw new rex_exception('rex-config: cache dir "' . dirname(self::$cacheFile) . '" is not writable!');
279 }
280
281
282 register_shutdown_function([self::class, 'save']);
283
284 self::load();
285 self::$initialized = true;
286 }
287
288 289 290
291 protected static function load()
292 {
293
294 if (!self::loadFromFile()) {
295
296 self::loadFromDb();
297
298 self::generateCache();
299 }
300 }
301
302 303 304 305 306
307 private static function loadFromFile()
308 {
309
310 if (file_exists(self::$cacheFile)) {
311 self::$data = rex_file::getCache(self::$cacheFile);
312 return true;
313 }
314 return false;
315 }
316
317 318 319
320 private static function loadFromDb()
321 {
322 $sql = rex_sql::factory();
323 $sql->setQuery('SELECT * FROM ' . rex::getTablePrefix() . 'config');
324
325 self::$data = [];
326 foreach ($sql as $cfg) {
327 self::$data[$cfg->getValue('namespace')][$cfg->getValue('key')] = json_decode($cfg->getValue('value'), true);
328 }
329 }
330
331 332 333
334 private static function generateCache()
335 {
336 if (rex_file::putCache(self::$cacheFile, self::$data) <= 0) {
337 throw new rex_exception('rex-config: unable to write cache file ' . self::$cacheFile);
338 }
339 }
340
341 342 343
344 public static function save()
345 {
346
347 if (!self::$changed) {
348 return;
349 }
350
351
352 if (empty(self::$deletedData) && empty(self::$changedData)) {
353 return;
354 }
355
356
357 rex_file::delete(self::$cacheFile);
358
359
360 self::saveToDb();
361 self::$changed = false;
362 self::$changedData = [];
363 self::$deletedData = [];
364 }
365
366 367 368
369 private static function saveToDb()
370 {
371 $sql = rex_sql::factory();
372
373
374
375 if (self::$deletedData) {
376 $sql->setTable(rex::getTable('config'));
377
378 $where = [];
379 $params = [];
380 foreach (self::$deletedData as $namespace => $nsData) {
381 $params = array_merge($params, [$namespace], array_keys($nsData));
382 $where[] = 'namespace = ? AND `key` IN ('.implode(', ', array_fill(0, count($nsData), '?')).')';
383 }
384
385 $sql->setWhere(implode("\n OR ", $where), $params);
386 $sql->delete();
387 }
388
389
390 if (self::$changedData) {
391 $sql->setTable(rex::getTable('config'));
392
393 foreach (self::$changedData as $namespace => $nsData) {
394 foreach ($nsData as $key => $value) {
395 $sql->addRecord(function (rex_sql $record) use ($namespace, $key, $value) {
396 $record->setValue('namespace', $namespace);
397 $record->setValue('key', $key);
398 $record->setValue('value', json_encode($value));
399 });
400 }
401 }
402
403 $sql->insertOrUpdate();
404 }
405 }
406 }
407