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