Passed
Push — master ( a4754c...e0c6ec )
by Nazar
05:32
created

Config::apply()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 0
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
ccs 10
cts 10
cp 1
crap 3
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2017, 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
		Properties_getter,
26
		Singleton;
27
	const INIT_STATE_METHOD = 'init';
28
	const SYSTEM_MODULE     = 'System';
29
	const SYSTEM_THEME      = 'CleverStyle';
30
	/**
31
	 * @var Cache\Prefix
32
	 */
33
	protected $cache;
34
	/**
35
	 * Most of general configuration properties
36
	 *
37
	 * @var array
38
	 */
39
	public $core = [];
40
	/**
41
	 * @var array
42
	 */
43
	protected $core_internal = [];
44
	/**
45
	 * @var string
46
	 */
47
	protected $last_language;
48
	/**
49
	 * Configuration of databases, except the main database, parameters of which are stored in configuration file
50
	 *
51
	 * @var mixed[]
52
	 */
53
	public $db = [];
54
	/**
55
	 * Configuration of storages, except the main storage, parameters of which are stored in configuration file
56
	 *
57
	 * @var mixed[]
58
	 */
59
	public $storage = [];
60
	/**
61
	 * Internal structure of components parameters
62
	 *
63
	 * @var array[]
64
	 */
65
	public $components = [];
66
	/**
67
	 * Array of all domains, which allowed to access the site
68
	 *
69
	 * Contains keys:
70
	 * * count - Total count
71
	 * * http - Insecure (http) domains
72
	 * * https - Secure (https) domains
73
	 *
74
	 * @var array
75
	 */
76
	public    $mirrors;
77
	protected $data_model = [
78
		'domain'     => 'text',
79
		'core'       => 'json',
80
		'db'         => 'json',
81
		'storage'    => 'json',
82
		'components' => 'json'
83
	];
84
	protected $table      = '[prefix]config';
85 9
	protected function cdb () {
86 9
		return 0;
87
	}
88
	/**
89
	 * Update multilingual options when language changes
90
	 */
91 108
	protected function init () {
92 108
		Event::instance()->on(
93 108
			'System/Language/change/after',
94
			function () {
95 81
				$this->read_core_update_multilingual();
96 108
			}
97
		);
98 108
	}
99
	/**
100
	 * Loading of configuration, initialization of $Config, $Cache, $L and Page objects, Routing processing
101
	 *
102
	 * @throws ExitException
103
	 */
104 108
	protected function construct () {
105 108
		$this->cache = Cache::prefix('config');
106 108
		Event::instance()->fire('System/Config/init/before');
107 108
		$this->load_configuration();
108 108
		Event::instance()->fire('System/Config/init/after');
109 108
		if (!file_exists(MODULES.'/'.$this->core['default_module'])) {
110 3
			$this->core['default_module'] = self::SYSTEM_MODULE;
111 3
			$this->save();
112
		}
113 108
	}
114
	/**
115
	 * Reloading of settings cache
116
	 *
117
	 * @throws ExitException
118
	 */
119 108
	protected function load_configuration () {
120
		/**
121
		 * @var array[] $config
122
		 */
123 108
		$config = $this->cache->get(
124 108
			'source',
125
			function () {
126 6
				return $this->read(Core::instance()->domain);
127 108
			}
128
		);
129 108
		if (!$config) {
0 ignored issues
show
introduced by
The condition ! $config can never be false.
Loading history...
130 3
			throw new ExitException('Failed to load system configuration', 500);
131
		}
132 108
		$this->core_internal = $config['core'] + Options::get_defaults();
133 108
		$this->core          = $this->core_internal;
134 108
		$this->db            = $config['db'];
135 108
		$this->storage       = $config['storage'];
136 108
		$this->components    = $config['components'];
137 108
		date_default_timezone_set($this->core['timezone']);
138 108
		$this->fill_mirrors();
139 108
		$this->read_core_update_multilingual(true);
140 108
	}
141
	/**
142
	 * Is used to fill `$this->mirrors` using current configuration
143
	 */
144 108
	protected function fill_mirrors () {
145 108
		$this->mirrors = [
146
			'count' => 0,
147
			'http'  => [],
148
			'https' => []
149
		];
150 108
		foreach ($this->core['url'] as $i => $address) {
151 108
			list($protocol, $urls) = explode('://', $address, 2);
152 108
			$urls                       = explode(';', $urls);
153 108
			$this->mirrors[$protocol][] = $urls[0];
154
		}
155 108
		$this->mirrors['count'] = count($this->mirrors['http']) + count($this->mirrors['https']);
156 108
	}
157
	/**
158
	 * @param bool $force
159
	 */
160 108
	protected function read_core_update_multilingual ($force = false) {
161 108
		if ($force) {
162 108
			$this->last_language = null;
163
		}
164 108
		$language = Language::instance()->clanguage ?: @$this->core['language'];
165 108
		if (!$language || $language == $this->last_language) {
166 81
			return;
167
		}
168 108
		$this->last_language  = $language;
169 108
		$multilingual_options = $this->cache->get(
170 108
			$language,
171 108
			function () {
172 18
				$db_id                = $this->module('System')->db('texts');
173 18
				$Text                 = Text::instance();
174 18
				$multilingual_options = [];
175 18
				foreach (Options::get_multilingual() as $option) {
176 18
					$multilingual_options[$option] = $Text->process($db_id, $this->core_internal[$option], true);
177
				}
178 18
				return $multilingual_options;
179 108
			}
180
		);
181 108
		$this->core           = $multilingual_options + $this->core;
182 108
	}
183
	/**
184
	 * Get core options item
185
	 *
186
	 * @param string[]|string[][] $item
187
	 *
188
	 * @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
189
	 *                            missing key will cause the whole thing to fail)
190
	 */
191 3
	public function core (...$item) {
192 3
		return $this->get_property_items('core', $item);
0 ignored issues
show
Bug introduced by
$item of type array<integer,array<mixed,string[]>|string[]> is incompatible with the type array<mixed,string[]>|string[] expected by parameter $items of cs\Config::get_property_items(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

192
		return $this->get_property_items('core', /** @scrutinizer ignore-type */ $item);
Loading history...
193
	}
194
	/**
195
	 * Applying settings without saving changes into db
196
	 *
197
	 * @return bool
198
	 *
199
	 * @throws ExitException
200
	 */
201 3
	public function apply () {
202 3
		$this->core = Options::apply_formatting($this->core) + Options::get_defaults();
203
		/**
204
		 * Update multilingual cache manually to avoid actually storing changes in database
205
		 */
206 3
		$multilingual_options_list = Options::get_multilingual();
207 3
		$multilingual_options      = [];
208 3
		foreach ($this->core as $option => $value) {
209 3
			if (in_array($option, $multilingual_options_list)) {
210 3
				$multilingual_options[$option] = $this->core[$option];
211
			} else {
212 3
				$this->core_internal[$option] = $value;
213
			}
214
		}
215 3
		$this->cache->set(Language::instance()->clanguage, $multilingual_options);
216 3
		return $this->apply_internal();
217
	}
218
	/**
219
	 * Applying settings without saving changes into db
220
	 *
221
	 * @param bool $cache_not_saved_mark
222
	 *
223
	 * @return bool
224
	 *
225
	 * @throws ExitException
226
	 */
227 6
	protected function apply_internal ($cache_not_saved_mark = true) {
228 6
		if ($cache_not_saved_mark) {
229 3
			$this->core_internal['cache_not_saved'] = true;
230
		} else {
231 6
			unset($this->core_internal['cache_not_saved']);
232
		}
233 6
		if (!$this->cache->set(
234 6
			'source',
235
			[
236 6
				'core'       => $this->core_internal,
237 6
				'db'         => $this->db,
238 6
				'storage'    => $this->storage,
239 6
				'components' => $this->components
240
			]
241
		)
242
		) {
243 3
			return false;
244
		}
245 6
		date_default_timezone_set($this->core['timezone']);
246 6
		$this->fill_mirrors();
247 6
		Event::instance()->fire('System/Config/changed');
248 6
		return true;
249
	}
250
	/**
251
	 * Saving settings
252
	 *
253
	 * @return bool
254
	 *
255
	 * @throws ExitException
256
	 */
257 6
	public function save () {
258 6
		unset($this->core_internal['cache_not_saved']);
259 6
		$this->core = Options::apply_formatting($this->core) + Options::get_defaults();
260
		/**
261
		 * Persist multilingual options and copy the rest to `$this->core_internal` as is
262
		 */
263 6
		$multilingual_options_list = Options::get_multilingual();
264 6
		$db_id                     = $this->module('System')->db('texts');
265 6
		$Text                      = Text::instance();
266 6
		foreach ($this->core as $option => $value) {
267 6
			if (in_array($option, $multilingual_options_list)) {
268 6
				$this->core_internal[$option] = $Text->set($db_id, 'System/Config/core', $option, $this->core[$option]);
269
			} else {
270 6
				$this->core_internal[$option] = $value;
271
			}
272
		}
273 6
		if (!$this->update(Core::instance()->domain, $this->core_internal, $this->db, $this->storage, $this->components)) {
0 ignored issues
show
Bug introduced by
cs\Core::instance()->domain of type string is incompatible with the type array expected by parameter $arguments of cs\Config::update(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

273
		if (!$this->update(/** @scrutinizer ignore-type */ Core::instance()->domain, $this->core_internal, $this->db, $this->storage, $this->components)) {
Loading history...
274 3
			return false;
275
		}
276 6
		$this->cache->del('/');
277 6
		return $this->apply_internal(false);
278
	}
279
	/**
280
	 * Whether configuration was applied (not saved) and can be canceled
281
	 *
282
	 * @return bool
283
	 */
284 3
	public function cancel_available () {
285 3
		return isset($this->core_internal['cache_not_saved']);
286
	}
287
	/**
288
	 * Canceling of applied settings
289
	 *
290
	 * @throws ExitException
291
	 */
292 3
	public function cancel () {
293 3
		$this->cache->del('/');
294 3
		$this->load_configuration();
295 3
	}
296
	/**
297
	 * Get base url of current mirror including language suffix
298
	 *
299
	 * @return string
300
	 */
301 12
	public function base_url () {
302 12
		if (Request::instance()->mirror_index === -1) {
303 3
			return '';
304
		}
305 12
		$base_url = $this->core_url();
306 12
		if ($this->core['multilingual']) {
307 6
			$L = Language::instance();
308 6
			$base_url .= "/$L->clang";
309
		}
310 12
		return $base_url;
311
	}
312
	/**
313
	 * Get base url of main domain
314
	 *
315
	 * @return string
316
	 */
317 30
	public function core_url () {
318 30
		$Request = Request::instance();
319 30
		return "$Request->scheme://$Request->host";
320
	}
321
	/**
322
	 * Get object for getting db and storage configuration of module
323
	 *
324
	 * @param string $module_name
325
	 *
326
	 * @return Config\Module_Properties
327
	 */
328 78
	public function module ($module_name) {
329 78
		if (!isset($this->components['modules'][$module_name])) {
330
			/** @noinspection PhpIncompatibleReturnTypeInspection */
331 3
			return False_class::instance();
332
		}
333 78
		return new Config\Module_Properties($this->components['modules'][$module_name], $module_name);
334
	}
335
}
336