Completed
Push — master ( 32eaad...6cdc53 )
by Nazar
04:10
created

Route::analyze_route_path()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 50
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 7.0957

Importance

Changes 0
Metric Value
cc 7
eloc 33
nc 24
nop 1
dl 0
loc 50
ccs 28
cts 32
cp 0.875
crap 7.0957
rs 6.7272
c 0
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 12
	function init_route () {
88 12
		$this->mirror_index    = -1;
89 12
		$this->path_normalized = '';
90 12
		$this->route           = [];
91 12
		$this->route_path      = [];
92 12
		$this->route_ids       = [];
93 12
		$this->cli_path        = false;
94 12
		$this->admin_path      = false;
95 12
		$this->api_path        = false;
96 12
		$this->current_module  = '';
97 12
		$this->home_page       = false;
98 12
		if ($this->cli) {
99
			$results = $this->analyze_route_path($this->path);
100
		} else {
101 12
			$Config             = Config::instance();
102 12
			$this->mirror_index = $this->determine_current_mirror_index($Config);
103
			/**
104
			 * If match was not found - mirror is not allowed!
105
			 */
106 12
			if ($this->mirror_index === -1) {
107
				throw new ExitException("Mirror $this->host not allowed", 400);
108
			}
109 12
			$results = $this->analyze_route_path($this->path);
110 12
			$this->handle_redirect($Config, $results['path_normalized']);
111
		}
112 12
		$this->route = $results['route'];
113
		/**
114
		 * Separate numeric and other parts of route
115
		 */
116 12
		foreach ($this->route as $item) {
117 4
			if (is_numeric($item)) {
118
				$this->route_ids[] = $item;
119
			} else {
120 4
				$this->route_path[] = $item;
121
			}
122
		}
123 12
		$this->path_normalized = $results['path_normalized'];
124 12
		$this->cli_path        = $results['cli_path'];
125 12
		$this->admin_path      = $results['admin_path'];
126 12
		$this->api_path        = $results['api_path'];
127 12
		$this->current_module  = $results['current_module'];
128 12
		$this->home_page       = $results['home_page'];
129 12
	}
130
	/**
131
	 * @param Config $Config
132
	 *
133
	 * @return int
134
	 */
135 12
	protected function determine_current_mirror_index ($Config) {
136
		/**
137
		 * Search for url matching in all mirrors
138
		 */
139 12
		foreach ($Config->core['url'] as $i => $address) {
140 12
			list($scheme, $urls) = explode('://', $address, 2);
141 12
			if ($scheme == $this->scheme) {
142 12
				foreach (explode(';', $urls) as $url) {
143 12
					if (mb_strpos("$this->host/$this->path", "$url/") === 0) {
144 12
						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 12
	function analyze_route_path ($path) {
162 12
		$rc = trim($path, '/');
163 12
		if (Language::instance()->url_language($rc)) {
164
			$rc = explode('/', $rc, 2);
165
			$rc = isset($rc[1]) ? $rc[1] : '';
166
		}
167 12
		Event::instance()->fire(
168 12
			'System/Request/routing_replace',
169
			[
170 12
				'rc' => &$rc
171
			]
172
		);
173
		/**
174
		 * Obtaining page path in form of array
175
		 */
176 12
		$rc         = $rc ? explode('/', $rc) : [];
177 12
		$cli_path   = '';
178 12
		$admin_path = '';
179 12
		$api_path   = '';
180 12
		$home_page  = false;
181
		/**
182
		 * If url is cli, admin or API page - set corresponding variables to corresponding path prefix
183
		 */
184 12
		if (@mb_strtolower($rc[0]) == 'cli') {
185
			$cli_path = 'cli/';
186
			array_shift($rc);
187 12
		} elseif (@mb_strtolower($rc[0]) == 'admin') {
188 2
			$admin_path = 'admin/';
189 2
			array_shift($rc);
190 10
		} elseif (@mb_strtolower($rc[0]) == 'api') {
191 4
			$api_path = 'api/';
192 4
			array_shift($rc);
193
		}
194
		/**
195
		 * Module detection
196
		 */
197 12
		$current_module = $this->determine_page_module($rc, $home_page, $cli_path, $admin_path, $api_path);
198
		return [
199 12
			'route'           => $rc,
200 12
			'path_normalized' => trim(
201 12
				$cli_path.$admin_path.$api_path.$current_module.'/'.implode('/', $rc),
202 12
				'/'
203
			),
204 12
			'cli_path'        => (bool)$cli_path,
205 12
			'admin_path'      => (bool)$admin_path,
206 12
			'api_path'        => (bool)$api_path,
207 12
			'current_module'  => $current_module,
208 12
			'home_page'       => $home_page
209
		];
210
	}
211
	/**
212
	 * @param Config $Config
213
	 * @param string $path_normalized
214
	 *
215
	 * @throws ExitException
216
	 */
217 12
	protected function handle_redirect ($Config, $path_normalized) {
218
		/**
219
		 * Redirection processing
220
		 */
221 12
		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 12
	}
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 string
269
	 */
270 12
	protected function determine_page_module (&$rc, &$home_page, $cli_path, $admin_path, $api_path) {
271 12
		$Config           = Config::instance();
272 12
		$modules          = $this->get_modules($Config, (bool)$admin_path);
273 12
		$module_specified = isset($rc[0]);
274 12
		if ($module_specified) {
275 4
			if (in_array($module_specified, $modules)) {
276 4
				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 8
		if ($cli_path || $admin_path || $api_path || $module_specified) {
287 2
			$current_module = 'System';
288
		} else {
289 6
			$current_module = $Config->core['default_module'];
290 6
			$home_page      = true;
291
		}
292 8
		return $current_module;
293
	}
294
	/**
295
	 * Get array of modules
296
	 *
297
	 * @param Config $Config
298
	 * @param bool   $admin_path
299
	 *
300
	 * @return string[]
301
	 */
302 12
	protected function get_modules ($Config, $admin_path) {
303 12
		$modules = array_filter(
304 12
			$Config->components['modules'],
305 12
			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 12
			}
316
		);
317 12
		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