Completed
Push — master ( daacaa...c58d39 )
by Nazar
04:28
created

Route::is_referer_local()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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