Completed
Push — master ( 97e73f...777a33 )
by Nazar
04:16
created

Route::determine_page_module()   C

Complexity

Conditions 9
Paths 6

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 11.9991

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 18
nc 6
nop 5
dl 0
loc 24
ccs 12
cts 18
cp 0.6667
crap 11.9991
rs 5.3563
c 1
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2015-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\Request;
9
use
10
	cs\Config,
11
	cs\Event,
12
	cs\ExitException,
13
	cs\Language,
14
	cs\Response;
15
16
/**
17
 * @property string $scheme
18
 * @property string $host
19
 * @property string $path
20
 */
21
trait Route {
22
	/**
23
	 * Current mirror according to configuration
24
	 *
25
	 * @var int
26
	 */
27
	public $mirror_index;
28
	/**
29
	 * Normalized processed representation of relative address, may differ from raw, should be used in most cases
30
	 *
31
	 * @var string
32
	 */
33
	public $path_normalized;
34
	/**
35
	 * Contains parsed route of current page url in form of array without module name and prefixes `admin|api`
36
	 *
37
	 * @var array
38
	 */
39
	public $route;
40
	/**
41
	 * Like `$route` property, but excludes numerical items
42
	 *
43
	 * @var string[]
44
	 */
45
	public $route_path;
46
	/**
47
	 * Like `$route` property, but only includes numerical items (opposite to route_path property)
48
	 *
49
	 * @var int[]
50
	 */
51
	public $route_ids;
52
	/**
53
	 * Request to administration section
54
	 *
55
	 * @var bool
56
	 */
57
	public $admin_path;
58
	/**
59
	 * Request to CLI interface
60
	 *
61
	 * @var bool
62
	 */
63
	public $cli_path;
64
	/**
65
	 * Request to api section
66
	 *
67
	 * @var bool
68
	 */
69
	public $api_path;
70
	/**
71
	 * Current module
72
	 *
73
	 * @var string
74
	 */
75
	public $current_module;
76
	/**
77
	 * Home page
78
	 *
79
	 * @var bool
80
	 */
81
	public $home_page;
82
	/**
83
	 * Initialize route based on system configuration, requires `::init_server()` being called first since uses its data
84
	 *
85
	 * @throws ExitException
86
	 */
87 8
	function init_route () {
88 8
		$this->mirror_index    = -1;
89 8
		$this->path_normalized = '';
90 8
		$this->route           = [];
91 8
		$this->route_path      = [];
92 8
		$this->route_ids       = [];
93 8
		$this->cli_path        = false;
94 8
		$this->admin_path      = false;
95 8
		$this->api_path        = false;
96 8
		$this->current_module  = '';
97 8
		$this->home_page       = false;
98 8
		if ($this->cli) {
99
			$results = $this->analyze_route_path($this->path);
100
		} else {
101 8
			$Config             = Config::instance();
102 8
			$this->mirror_index = $this->determine_current_mirror_index($Config);
103
			/**
104
			 * If match was not found - mirror is not allowed!
105
			 */
106 8
			if ($this->mirror_index === -1) {
107
				throw new ExitException("Mirror $this->host not allowed", 400);
108
			}
109 8
			$results = $this->analyze_route_path($this->path);
110 8
			$this->handle_redirect($Config, $results['path_normalized']);
111
		}
112 8
		$this->route = $results['route'];
113
		/**
114
		 * Separate numeric and other parts of route
115
		 */
116 8
		foreach ($this->route as $item) {
117 2
			if (is_numeric($item)) {
118
				$this->route_ids[] = $item;
119
			} else {
120 2
				$this->route_path[] = $item;
121
			}
122
		}
123 8
		$this->path_normalized = $results['path_normalized'];
124 8
		$this->cli_path        = $results['cli_path'];
125 8
		$this->admin_path      = $results['admin_path'];
126 8
		$this->api_path        = $results['api_path'];
127 8
		$this->current_module  = $results['current_module'];
128 8
		$this->home_page       = $results['home_page'];
129 8
	}
130
	/**
131
	 * @param Config $Config
132
	 *
133
	 * @return int
134
	 */
135 8
	protected function determine_current_mirror_index ($Config) {
136
		/**
137
		 * Search for url matching in all mirrors
138
		 */
139 8
		foreach ($Config->core['url'] as $i => $address) {
140 8
			list($scheme, $urls) = explode('://', $address, 2);
141 8
			if ($scheme == $this->scheme) {
142 8
				foreach (explode(';', $urls) as $url) {
143 8
					if (mb_strpos("$this->host/$this->path", "$url/") === 0) {
144 8
						return $i;
145
					}
146
				}
147
			}
148
		}
149
		return -1;
150
	}
151
	/**
152
	 * Process raw relative route.
153
	 *
154
	 * As result returns current route in system in form of array, normalized path, detects module path points to, whether this is API call, administration
155
	 * page, or home page whether this is API call, admin page, or home page
156
	 *
157
	 * @param string $path
158
	 *
159
	 * @return array Array contains next elements: `route`, `path_normalized`, `cli_path`, `admin_path`, `api_path`, `current_module`, `home_page`
160
	 */
161 8
	function analyze_route_path ($path) {
162 8
		$rc = trim($path, '/');
163 8
		if (Language::instance()->url_language($rc)) {
164
			$rc = explode('/', $rc, 2);
165
			$rc = isset($rc[1]) ? $rc[1] : '';
166
		}
167 8
		Event::instance()->fire(
168 8
			'System/Request/routing_replace',
169
			[
170 8
				'rc' => &$rc
171
			]
172
		);
173
		/**
174
		 * Obtaining page path in form of array
175
		 */
176 8
		$rc         = $rc ? explode('/', $rc) : [];
177 8
		$cli_path   = '';
178 8
		$admin_path = '';
179 8
		$api_path   = '';
180 8
		$home_page  = false;
181
		/**
182
		 * If url is cli, admin or API page - set corresponding variables to corresponding path prefix
183
		 */
184 8
		if (@mb_strtolower($rc[0]) == 'cli') {
185
			$cli_path = 'cli/';
186
			array_shift($rc);
187 8
		} elseif (@mb_strtolower($rc[0]) == 'admin') {
188 2
			$admin_path = 'admin/';
189 2
			array_shift($rc);
190 6
		} elseif (@mb_strtolower($rc[0]) == 'api') {
191 2
			$api_path = 'api/';
192 2
			array_shift($rc);
193
		}
194
		/**
195
		 * Module detection
196
		 */
197 8
		$current_module = $this->determine_page_module($rc, $home_page, $cli_path, $admin_path, $api_path);
198
		return [
199 8
			'route'           => $rc,
200 8
			'path_normalized' => trim(
201 8
				$cli_path.$admin_path.$api_path.$current_module.'/'.implode('/', $rc),
202 8
				'/'
203
			),
204 8
			'cli_path'        => (bool)$cli_path,
205 8
			'admin_path'      => (bool)$admin_path,
206 8
			'api_path'        => (bool)$api_path,
207 8
			'current_module'  => $current_module,
208 8
			'home_page'       => $home_page
209
		];
210
	}
211
	/**
212
	 * @param Config $Config
213
	 * @param string $path_normalized
214
	 *
215
	 * @throws ExitException
216
	 */
217 8
	protected function handle_redirect ($Config, $path_normalized) {
218
		/**
219
		 * Redirection processing
220
		 */
221 8
		if (strpos($path_normalized, 'System/redirect/') === 0) {
222
			if ($this->is_referer_local($Config)) {
223
				Response::instance()->redirect(
224
					substr($path_normalized, 16),
225
					301
226
				);
227
				throw new ExitException;
228
			} else {
229
				throw new ExitException(400);
230
			}
231
		}
232 8
	}
233
	/**
234
	 * Check whether referer is local
235
	 *
236
	 * @param Config $Config
237
	 *
238
	 * @return bool
239
	 */
240
	protected function is_referer_local ($Config) {
241
		$referer = $this->header('referer');
242
		if (!$referer) {
243
			return false;
244
		}
245
		list($referer_protocol, $referer_host) = explode('://', $referer);
246
		$referer_host = explode('/', $referer_host)[0];
247
		foreach ($Config->core['url'] as $address) {
248
			list($protocol, $urls) = explode('://', $address, 2);
249
			if ($protocol === $referer_protocol) {
250
				foreach (explode(';', $urls) as $url) {
251
					if (mb_strpos($referer_host, $url) === 0) {
252
						return true;
253
					}
254
				}
255
			}
256
		}
257
		return false;
258
	}
259
	/**
260
	 * Determine module of current page based on page path and system configuration
261
	 *
262
	 * @param array  $rc
263
	 * @param bool   $home_page
264
	 * @param string $cli_path
265
	 * @param string $admin_path
266
	 * @param string $api_path
267
	 *
268
	 * @return mixed|string
269
	 */
270 8
	protected function determine_page_module (&$rc, &$home_page, $cli_path, $admin_path, $api_path) {
271 8
		$Config           = Config::instance();
272 8
		$modules          = $this->get_modules($Config, (bool)$admin_path);
273 8
		$module_specified = isset($rc[0]);
274 8
		if ($module_specified) {
275 2
			if (in_array($module_specified, $modules)) {
276 2
				return array_shift($rc);
277
			}
278
			$L = Language::instance();
279
			foreach ($modules as $module) {
280
				if ($module_specified == path($L->$module)) {
281
					array_shift($rc);
282
					return $module;
283
				}
284
			}
285
		}
286 6
		if ($cli_path || $admin_path || $api_path || $module_specified) {
287 2
			$current_module = 'System';
288
		} else {
289 4
			$current_module = $Config->core['default_module'];
290 4
			$home_page      = true;
291
		}
292 6
		return $current_module;
293
	}
294
	/**
295
	 * Get array of modules
296
	 *
297
	 * @param Config $Config
298
	 * @param bool   $admin_path
299
	 *
300
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<integer|string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
301
	 */
302 8
	protected function get_modules ($Config, $admin_path) {
303 8
		$modules = array_filter(
304 8
			$Config->components['modules'],
305 8
			function ($module_data) use ($admin_path) {
306
				/**
307
				 * Skip uninstalled modules and modules that are disabled (on all pages except admin pages)
308
				 */
309
				return
310
					(
311
						$admin_path &&
312
						$module_data['active'] == Config\Module_Properties::DISABLED
313
					) ||
314
					$module_data['active'] == Config\Module_Properties::ENABLED;
315 8
			}
316
		);
317 8
		return array_keys($modules);
318
	}
319
	/**
320
	 * Get route part by index
321
	 *
322
	 * @param int $index
323
	 *
324
	 * @return int|null|string
325
	 */
326
	function route ($index) {
327
		return @$this->route[$index];
328
	}
329
	/**
330
	 * Get route path part by index
331
	 *
332
	 * @param int $index
333
	 *
334
	 * @return null|string
335
	 */
336
	function route_path ($index) {
337
		return @$this->route_path[$index];
338
	}
339
	/**
340
	 * Get route ids part by index
341
	 *
342
	 * @param int $index
343
	 *
344
	 * @return int|null
345
	 */
346
	function route_ids ($index) {
347
		return @$this->route_ids[$index];
348
	}
349
}
350