Completed
Push — master ( e929f3...7e5e6c )
by Nazar
05:31
created

Route::is_referer_local()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6

Importance

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