Completed
Push — master ( fba85e...f9ec74 )
by Nazar
03:59
created

Route::route_ids()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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