Completed
Push — master ( 6d3a78...b7e47b )
by Nazar
04:38
created

Config::save()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 2
Metric Value
cc 4
eloc 15
c 2
b 0
f 2
nc 6
nop 0
dl 0
loc 24
ccs 14
cts 14
cp 1
crap 4
rs 8.6845
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs;
9
use
10
	cs\Config\Options;
11
12
/**
13
 * Provides next events:
14
 *  System/Config/init/before
15
 *
16
 *  System/Config/init/after
17
 *
18
 *  System/Config/changed
19
 *
20
 * @method static $this instance($check = false)
21
 */
22
class Config {
23
	use
24
		CRUD,
25
		Singleton;
26
	const INIT_STATE_METHOD = 'init';
27
	const SYSTEM_MODULE     = 'System';
28
	const SYSTEM_THEME      = 'CleverStyle';
29
	/**
30
	 * @var Cache\Prefix
31
	 */
32
	protected $cache;
33
	/**
34
	 * Most of general configuration properties
35
	 *
36
	 * @var array
37
	 */
38
	public $core = [];
39
	/**
40
	 * @var array
41
	 */
42
	protected $core_internal = [];
43
	/**
44
	 * @var string
45
	 */
46
	protected $last_language;
47
	/**
48
	 * Configuration of databases, except the main database, parameters of which are stored in configuration file
49
	 *
50
	 * @var mixed[]
51
	 */
52
	public $db = [];
53
	/**
54
	 * Configuration of storages, except the main storage, parameters of which are stored in configuration file
55
	 *
56
	 * @var mixed[]
57
	 */
58
	public $storage = [];
59
	/**
60
	 * Internal structure of components parameters
61
	 *
62
	 * @var array[]
63
	 */
64
	public $components = [];
65
	/**
66
	 * Array of all domains, which allowed to access the site
67
	 *
68
	 * Contains keys:
69
	 * * count - Total count
70
	 * * http - Insecure (http) domains
71
	 * * https - Secure (https) domains
72
	 *
73
	 * @var array
74
	 */
75
	public    $mirrors;
76
	protected $data_model = [
77
		'domain'     => 'text',
78
		'core'       => 'json',
79
		'db'         => 'json',
80
		'storage'    => 'json',
81
		'components' => 'json'
82
	];
83
	protected $table      = '[prefix]config';
84 4
	protected function cdb () {
85 4
		return 0;
86
	}
87
	/**
88
	 * Update multilingual options when language changes
89
	 */
90 54
	protected function init () {
91 54
		$this->read_core_update_multilingual();
92 54
		Event::instance()->on(
93 54
			'System/Language/change/after',
94
			function () {
95 44
				$this->read_core_update_multilingual();
96 54
			}
97
		);
98 54
	}
99
	/**
100
	 * Loading of configuration, initialization of $Config, $Cache, $L and Page objects, Routing processing
101
	 *
102
	 * @throws ExitException
103
	 */
104 54
	protected function construct () {
105
		// TODO: Change `config2` to `config` in 6.x
106 54
		$this->cache = Cache::prefix('config2');
107 54
		Event::instance()->fire('System/Config/init/before');
108 54
		$this->load_configuration();
109 54
		Event::instance()->fire('System/Config/init/after');
110 54
		if (!file_exists(MODULES.'/'.$this->core['default_module'])) {
111 2
			$this->core['default_module'] = self::SYSTEM_MODULE;
112 2
			$this->save();
113
		}
114 54
	}
115
	/**
116
	 * Reloading of settings cache
117
	 *
118
	 * @throws ExitException
119
	 */
120 54
	protected function load_configuration () {
121
		/**
122
		 * @var array[] $config
123
		 */
124 54
		$config = $this->cache->get(
125 54
			'source',
126
			function () {
127 4
				return $this->read(Core::instance()->domain);
128 54
			}
129
		);
130 54
		if (!$config) {
131 2
			throw new ExitException('Failed to load system configuration', 500);
132
		}
133 54
		$this->core_internal = $config['core'];
134 54
		$this->core          = $this->core_internal;
135 54
		$this->db            = $config['db'];
136 54
		$this->storage       = $config['storage'];
137 54
		$this->components    = $config['components'];
138 54
		$this->core += Options::get_defaults();
139 54
		date_default_timezone_set($this->core['timezone']);
140 54
		$this->fill_mirrors();
141 54
	}
142
	/**
143
	 * Is used to fill `$this->mirrors` using current configuration
144
	 */
145 54
	protected function fill_mirrors () {
146 54
		$this->mirrors = [
147
			'count' => 0,
148
			'http'  => [],
149
			'https' => []
150
		];
151 54
		foreach ($this->core['url'] as $i => $address) {
152 54
			list($protocol, $urls) = explode('://', $address, 2);
153 54
			$urls                       = explode(';', $urls);
154 54
			$this->mirrors[$protocol][] = $urls[0];
155
		}
156 54
		$this->mirrors['count'] = count($this->mirrors['http']) + count($this->mirrors['https']);
157 54
	}
158 54
	protected function read_core_update_multilingual () {
159 54
		$language = Language::instance(true)->clanguage ?: @$this->core['language'];
160 54
		if (!$language || $language == $this->last_language) {
161 48
			return;
162
		}
163 52
		$this->last_language  = $language;
164 52
		$multilingual_options = $this->cache->get(
165
			$language,
166 52
			function () {
167 8
				$db_id                = $this->module('System')->db('texts');
168 8
				$Text                 = Text::instance();
169 8
				$multilingual_options = [];
170 8
				foreach (Options::get_multilingual() as $option) {
171 8
					$multilingual_options[$option] = $Text->process($db_id, $this->core_internal[$option], true);
172
				}
173 8
				return $multilingual_options;
174 52
			}
175
		);
176 52
		$this->core           = $multilingual_options + $this->core;
177 52
	}
178
	/**
179
	 * Get core options item
180
	 *
181
	 * @param string[]|string[][] $item
182
	 *
183
	 * @return mixed|mixed[]|null Core options items (or associative array of items) if exists or `null` otherwise (in case if `$item` is an array even one
184
	 *                            missing key will cause the whole thing to fail)
185
	 */
186 2
	public function core (...$item) {
187 2
		if (count($item) === 1) {
188 2
			$item = $item[0];
189
		}
190
		/**
191
		 * @var string|string[] $item
192
		 */
193 2
		if (is_array($item)) {
194 2
			$result = [];
195 2
			foreach ($item as &$i) {
196 2
				if (!isset($this->core[$i])) {
197 2
					return null;
198
				}
199 2
				$result[$i] = $this->core[$i];
200
			}
201 2
			return $result;
202
		}
203
		/** @noinspection OffsetOperationsInspection */
204 2
		return @$this->core[$item];
205
	}
206
	/**
207
	 * Applying settings without saving changes into db
208
	 *
209
	 * @return bool
210
	 *
211
	 * @throws ExitException
212
	 */
213 2
	public function apply () {
214 2
		$this->core = Options::apply_formatting($this->core) + Options::get_defaults();
215
		/**
216
		 * Update multilingual cache manually to avoid actually storing changes in database
217
		 */
218 2
		$multilingual_options_list = Options::get_multilingual();
219 2
		$multilingual_options      = [];
220 2
		foreach ($this->core as $option => $value) {
221 2
			if (in_array($option, $multilingual_options_list)) {
222 2
				$multilingual_options[$option] = $this->core[$option];
223
			} else {
224 2
				$this->core_internal[$option] = $value;
225
			}
226
		}
227 2
		$this->cache->set(Language::instance()->clanguage, $multilingual_options);
228 2
		return $this->apply_internal();
229
	}
230
	/**
231
	 * Applying settings without saving changes into db
232
	 *
233
	 * @param bool $cache_not_saved_mark
234
	 *
235
	 * @return bool
236
	 *
237
	 * @throws ExitException
238
	 */
239 2
	protected function apply_internal ($cache_not_saved_mark = true) {
240 2
		if ($cache_not_saved_mark) {
241 2
			$this->core_internal['cache_not_saved'] = true;
242
		} else {
243 2
			unset($this->core_internal['cache_not_saved']);
244
		}
245 2
		if (!$this->cache->set(
246 2
			'source',
247
			[
248 2
				'core'       => $this->core_internal,
249 2
				'db'         => $this->db,
250 2
				'storage'    => $this->storage,
251 2
				'components' => $this->components
252
			]
253
		)
254
		) {
255 2
			return false;
256
		}
257 2
		date_default_timezone_set($this->core['timezone']);
258 2
		$this->fill_mirrors();
259 2
		Event::instance()->fire('System/Config/changed');
260 2
		return true;
261
	}
262
	/**
263
	 * Saving settings
264
	 *
265
	 * @return bool
266
	 *
267
	 * @throws ExitException
268
	 */
269 2
	public function save () {
270 2
		unset($this->core_internal['cache_not_saved']);
271
		// TODO: Remove `modules/System/core_settings_defaults.json` file in 6.x
272 2
		$core_settings_defaults = Options::get_defaults();
273 2
		$this->core             = Options::apply_formatting($this->core) + $core_settings_defaults;
274
275
		/**
276
		 * Persist multilingual options and copy the rest to `$this->core_internal` as is
277
		 */
278 2
		$multilingual_options_list = Options::get_multilingual();
279 2
		$db_id                     = $this->module('System')->db('texts');
280 2
		$Text                      = Text::instance();
281 2
		foreach ($this->core as $option => $value) {
282 2
			if (in_array($option, $multilingual_options_list)) {
283 2
				$this->core_internal[$option] = $Text->set($db_id, 'System/Config/core', $option, $this->core[$option]);
284
			} else {
285 2
				$this->core_internal[$option] = $value;
286
			}
287
		}
288 2
		if (!$this->update(Core::instance()->domain, $this->core_internal, $this->db, $this->storage, $this->components)) {
289 2
			return false;
290
		}
291 2
		return $this->apply_internal(false);
292
	}
293
	/**
294
	 * Whether configuration was applied (not saved) and can be canceled
295
	 *
296
	 * @return bool
297
	 */
298 2
	public function cancel_available () {
299 2
		return isset($this->core_internal['cache_not_saved']);
300
	}
301
	/**
302
	 * Canceling of applied settings
303
	 *
304
	 * @throws ExitException
305
	 */
306 2
	public function cancel () {
307 2
		$this->cache->del('/');
308 2
		$this->load_configuration();
309 2
	}
310
	/**
311
	 * Get base url of current mirror including language suffix
312
	 *
313
	 * @return string
314
	 */
315 8
	public function base_url () {
316 8
		if (Request::instance()->mirror_index === -1) {
317 2
			return '';
318
		}
319 8
		$base_url = $this->core_url();
320 8
		if ($this->core['multilingual']) {
321 4
			$L = Language::instance();
322 4
			$base_url .= "/$L->clang";
323
		}
324 8
		return $base_url;
325
	}
326
	/**
327
	 * Get base url of main domain
328
	 *
329
	 * @return string
330
	 */
331 14
	public function core_url () {
332 14
		$Request = Request::instance();
333 14
		return "$Request->scheme://$Request->host";
334
	}
335
	/**
336
	 * Get object for getting db and storage configuration of module
337
	 *
338
	 * @param string $module_name
339
	 *
340
	 * @return Config\Module_Properties
341
	 */
342 54
	public function module ($module_name) {
343 54
		if (!isset($this->components['modules'][$module_name])) {
344
			/** @noinspection PhpIncompatibleReturnTypeInspection */
345 2
			return False_class::instance();
346
		}
347 54
		return new Config\Module_Properties($this->components['modules'][$module_name], $module_name);
348
	}
349
}
350