Completed
Push — master ( 9f103c...960d95 )
by Nazar
04:37
created

Route::split_route()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 15
ccs 8
cts 8
cp 1
crap 3
rs 9.4285
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\Request\Route\Static_files,
15
	cs\Response;
16
17
/**
18
 * @property bool   $cli
19
 * @property string $scheme
20
 * @property string $host
21
 * @property string $path
22
 *
23
 * @method string header(string $name)
24
 */
25
trait Route {
26
	use
27
		Static_files;
28
	/**
29
	 * Current mirror according to configuration
30
	 *
31
	 * @var int
32
	 */
33
	public $mirror_index;
34
	/**
35
	 * Normalized processed representation of relative address, may differ from raw, should be used in most cases
36
	 *
37
	 * @var string
38
	 */
39
	public $path_normalized;
40
	/**
41
	 * Contains parsed route of current page url in form of array without module name and prefixes `admin|api`
42
	 *
43
	 * @var array
44
	 */
45
	public $route;
46
	/**
47
	 * Like `$route` property, but excludes numerical items
48
	 *
49
	 * @var string[]
50
	 */
51
	public $route_path;
52
	/**
53
	 * Like `$route` property, but only includes numerical items (opposite to route_path property)
54
	 *
55
	 * @var int[]
56
	 */
57
	public $route_ids;
58
	/**
59
	 * Request to administration section
60
	 *
61
	 * @var bool
62
	 */
63
	public $admin_path;
64
	/**
65
	 * Request to CLI interface
66
	 *
67
	 * @var bool
68
	 */
69
	public $cli_path;
70
	/**
71
	 * Request to api section
72
	 *
73
	 * @var bool
74
	 */
75
	public $api_path;
76
	/**
77
	 * Current module
78
	 *
79
	 * @var string
80
	 */
81
	public $current_module;
82
	/**
83
	 * Home page
84
	 *
85
	 * @var bool
86
	 */
87
	public $home_page;
88
	/**
89
	 * Initialize route based on system configuration, requires `::init_server()` being called first since uses its data
90
	 *
91
	 * @throws ExitException
92
	 */
93 30
	public function init_route () {
94
		/**
95
		 * Serve static files here for early exit
96
		 */
97 30
		$this->serve_static_files();
98 30
		$this->mirror_index    = -1;
99 30
		$this->path_normalized = '';
100 30
		$this->route           = [];
101 30
		$this->route_path      = [];
102 30
		$this->route_ids       = [];
103 30
		$this->cli_path        = false;
104 30
		$this->admin_path      = false;
105 30
		$this->api_path        = false;
106 30
		$this->current_module  = '';
107 30
		$this->home_page       = false;
108 30
		if ($this->cli) {
109 4
			$results = $this->analyze_route_path($this->path);
110
		} else {
111 28
			$Config             = Config::instance();
112 28
			$this->mirror_index = $this->determine_current_mirror_index($Config);
113
			/**
114
			 * If match was not found - mirror is not allowed!
115
			 */
116 28
			if ($this->mirror_index === -1) {
117 2
				throw new ExitException("Mirror $this->host not allowed", 400);
118
			}
119 28
			$results = $this->analyze_route_path($this->path);
120 28
			$this->handle_redirect($Config, $results['path_normalized']);
121
		}
122 28
		$this->route           = $results['route'];
123 28
		$this->route_path      = $results['route_path'];
124 28
		$this->route_ids       = $results['route_ids'];
125 28
		$this->path_normalized = $results['path_normalized'];
126 28
		$this->cli_path        = $results['cli_path'];
127 28
		$this->admin_path      = $results['admin_path'];
128 28
		$this->api_path        = $results['api_path'];
129 28
		$this->current_module  = $results['current_module'];
130 28
		$this->home_page       = $results['home_page'];
131 28
	}
132
	/**
133
	 * @param Config $Config
134
	 *
135
	 * @return int
136
	 */
137 28
	protected function determine_current_mirror_index ($Config) {
138
		/**
139
		 * Search for url matching in all mirrors
140
		 */
141 28
		foreach ($Config->core['url'] as $i => $address) {
142 28
			list($scheme, $urls) = explode('://', $address, 2);
143 28
			if ($scheme == $this->scheme) {
144 28
				foreach (explode(';', $urls) as $url) {
145 28
					if (mb_strpos("$this->host/$this->path", "$url/") === 0) {
146 28
						return $i;
147
					}
148
				}
149
			}
150
		}
151 2
		return -1;
152
	}
153
	/**
154
	 * Process raw relative route.
155
	 *
156
	 * As result returns current route in system in form of array, normalized path, detects module path points to, whether this is API call, administration
157
	 * page, or home page whether this is API call, admin page, or home page
158
	 *
159
	 * @param string $path
160
	 *
161
	 * @return array Array contains next elements: `route`, `path_normalized`, `cli_path`, `admin_path`, `api_path`, `current_module`, `home_page`
162
	 */
163 30
	public function analyze_route_path ($path) {
164 30
		$route = trim($path, '/');
165 30
		Event::instance()->fire(
166 30
			'System/Request/routing_replace/before',
167
			[
168 30
				'rc' => &$route
169
			]
170
		);
171 28
		if (Language::instance()->url_language($route)) {
172 2
			$route = explode('/', $route, 2);
173 2
			$route = isset($route[1]) ? $route[1] : '';
174
		}
175
		/**
176
		 * Obtaining page path in form of array
177
		 */
178 28
		$route      = $route ? explode('/', $route) : [];
179 28
		$cli_path   = '';
180 28
		$admin_path = '';
181 28
		$api_path   = '';
182 28
		$home_page  = false;
183
		/**
184
		 * If url is cli, admin or API page - set corresponding variables to corresponding path prefix
185
		 */
186 28
		if ($this->cli && @mb_strtolower($route[0]) == 'cli') {
187 2
			$cli_path = 'cli/';
188 2
			array_shift($route);
189 28
		} elseif (@mb_strtolower($route[0]) == 'admin') {
190 4
			$admin_path = 'admin/';
191 4
			array_shift($route);
192 26
		} elseif (@mb_strtolower($route[0]) == 'api') {
193 6
			$api_path = 'api/';
194 6
			array_shift($route);
195
		}
196
		/**
197
		 * Module detection
198
		 */
199 28
		$current_module = $this->determine_page_module($route, $home_page, $cli_path, $admin_path, $api_path);
200 28
		list($route_path, $route_ids) = $this->split_route($route);
201 28
		$rc             = implode('/', $route);
202 28
		$old_rc         = $rc;
203 28
		$old_route      = $route;
204 28
		$old_route_path = $route_path;
205 28
		$old_route_ids  = $route_ids;
206 28
		Event::instance()->fire(
207 28
			'System/Request/routing_replace/after',
208
			[
209 28
				'rc'             => &$rc, // TODO: Deprecated key, remove in 6.x
210 28
				'route'          => &$route,
211 28
				'route_path'     => &$route_path,
212 28
				'route_ids'      => &$route_ids,
213 28
				'cli_path'       => &$cli_path,
214 28
				'admin_path'     => &$admin_path,
215 28
				'api_path'       => &$api_path,
216 28
				'regular_path'   => !($cli_path || $admin_path || $api_path),
217 28
				'current_module' => &$current_module,
218 28
				'home_page'      => &$home_page
219
			]
220
		);
221
		// TODO: Deprecated, remove in 6.x
222 28
		if ($rc != $old_rc) {
223
			$route = explode('/', $rc);
224
			list($route_path, $route_ids) = $this->split_route($route);
225
		}
226 28
		if ($route != $old_route && $route_path == $old_route_path && $route_ids == $old_route_ids) {
227
			list($route_path, $route_ids) = $this->split_route($route);
228
		}
229
		return [
230 28
			'route'           => $route,
231 28
			'route_path'      => $route_path,
232 28
			'route_ids'       => $route_ids,
233 28
			'path_normalized' => trim(
234 28
				"$cli_path$admin_path$api_path$current_module/$rc",
235 28
				'/'
236
			),
237 28
			'cli_path'        => (bool)$cli_path,
238 28
			'admin_path'      => (bool)$admin_path,
239 28
			'api_path'        => (bool)$api_path,
240 28
			'current_module'  => $current_module,
241 28
			'home_page'       => $home_page
242
		];
243
	}
244
	/**
245
	 * @param array $route
246
	 *
247
	 * @return array[] Key `0` contains array of paths, key `1` contains array of identifiers
248
	 */
249 28
	protected function split_route ($route) {
250 28
		$route_path = [];
251 28
		$route_ids  = [];
252
		/**
253
		 * Separate numeric and other parts of route
254
		 */
255 28
		foreach ($route as $item) {
256 6
			if (is_numeric($item)) {
257 2
				$route_ids[] = $item;
258
			} else {
259 6
				$route_path[] = $item;
260
			}
261
		}
262 28
		return [$route_path, $route_ids];
263
	}
264
	/**
265
	 * @param Config $Config
266
	 * @param string $path_normalized
267
	 *
268
	 * @throws ExitException
269
	 */
270 28
	protected function handle_redirect ($Config, $path_normalized) {
271
		/**
272
		 * Redirection processing
273
		 */
274 28
		if (strpos($path_normalized, 'System/redirect/') === 0) {
275 2
			if ($this->is_referer_local($Config)) {
276 2
				Response::instance()->redirect(
277 2
					substr($path_normalized, 16),
278 2
					301
279
				);
280 2
				throw new ExitException;
281
			} else {
282 2
				throw new ExitException(400);
283
			}
284
		}
285 28
	}
286
	/**
287
	 * Check whether referer is local
288
	 *
289
	 * @param Config $Config
290
	 *
291
	 * @return bool
292
	 */
293 2
	protected function is_referer_local ($Config) {
294 2
		$referer = $this->header('referer');
295 2
		if (!$referer) {
296 2
			return false;
297
		}
298 2
		list($referer_protocol, $referer_host) = explode('://', $referer);
299 2
		$referer_host = explode('/', $referer_host)[0];
300 2
		foreach ($Config->core['url'] as $address) {
301 2
			list($protocol, $urls) = explode('://', $address, 2);
302 2
			if ($protocol === $referer_protocol) {
303 2
				foreach (explode(';', $urls) as $url) {
304 2
					if (mb_strpos("$referer_host/", "$url/") === 0) {
305 2
						return true;
306
					}
307
				}
308
			}
309
		}
310 2
		return false;
311
	}
312
	/**
313
	 * Determine module of current page based on page path and system configuration
314
	 *
315
	 * @param array  $rc
316
	 * @param bool   $home_page
317
	 * @param string $cli_path
318
	 * @param string $admin_path
319
	 * @param string $api_path
320
	 *
321
	 * @return string
322
	 */
323 28
	protected function determine_page_module (&$rc, &$home_page, $cli_path, $admin_path, $api_path) {
324 28
		$Config           = Config::instance();
325 28
		$modules          = $this->get_modules($Config, (bool)$admin_path);
326 28
		$module_specified = @$rc[0];
327 28
		if ($module_specified) {
328 6
			if (in_array($module_specified, $modules)) {
329 6
				return array_shift($rc);
330
			}
331 2
			$L = Language::instance();
332 2
			foreach ($modules as $module) {
333 2
				if ($module_specified == path($L->$module)) {
334 2
					array_shift($rc);
335 2
					return $module;
336
				}
337
			}
338
		}
339 24
		if ($cli_path || $admin_path || $api_path || $module_specified) {
340 4
			$current_module = 'System';
341
		} else {
342 22
			$current_module = $Config->core['default_module'];
343 22
			$home_page      = true;
344
		}
345 24
		return $current_module;
346
	}
347
	/**
348
	 * Get array of modules
349
	 *
350
	 * @param Config $Config
351
	 * @param bool   $admin_path
352
	 *
353
	 * @return string[]
354
	 */
355 28
	protected function get_modules ($Config, $admin_path) {
356 28
		$modules = array_filter(
357 28
			$Config->components['modules'],
358 28
			function ($module_data) use ($admin_path) {
359
				/**
360
				 * Skip uninstalled modules and modules that are disabled (on all pages except admin pages)
361
				 */
362
				return
363
					(
364 28
						$admin_path &&
365 4
						$module_data['active'] == Config\Module_Properties::DISABLED
366
					) ||
367 28
					$module_data['active'] == Config\Module_Properties::ENABLED;
368 28
			}
369
		);
370 28
		return array_keys($modules);
371
	}
372
	/**
373
	 * Get route part by index
374
	 *
375
	 * @param int $index
376
	 *
377
	 * @return int|null|string
378
	 */
379 2
	public function route ($index) {
380 2
		return @$this->route[$index];
381
	}
382
	/**
383
	 * Get route path part by index
384
	 *
385
	 * @param int $index
386
	 *
387
	 * @return null|string
388
	 */
389 2
	public function route_path ($index) {
390 2
		return @$this->route_path[$index];
391
	}
392
	/**
393
	 * Get route ids part by index
394
	 *
395
	 * @param int $index
396
	 *
397
	 * @return int|null
398
	 */
399 2
	public function route_ids ($index) {
400 2
		return @$this->route_ids[$index];
401
	}
402
}
403