Completed
Push — master ( 17bd20...4edca1 )
by Nazar
04:17
created

Route::determine_current_mirror_index()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 16
rs 8.8571
cc 5
eloc 8
nc 5
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 CLI interface
64
	 *
65
	 * @var bool
66
	 */
67
	public $cli_path;
68
	/**
69
	 * Request to api section
70
	 *
71
	 * @var bool
72
	 */
73
	public $api_path;
74
	/**
75
	 * Current module
76
	 *
77
	 * @var string
78
	 */
79
	public $current_module;
80
	/**
81
	 * Home page
82
	 *
83
	 * @var bool
84
	 */
85
	public $home_page;
86
	/**
87
	 * Initialize route based on system configuration, requires `::init_server()` being called first since uses its data
88
	 *
89
	 * @throws ExitException
90
	 */
91
	function init_route () {
92
		$this->mirror_index    = -1;
93
		$this->path_normalized = '';
94
		$this->route           = [];
95
		$this->route_path      = [];
96
		$this->route_ids       = [];
97
		$this->cli_path        = false;
98
		$this->admin_path      = false;
99
		$this->api_path        = false;
100
		$this->current_module  = '';
101
		$this->home_page       = false;
102
		if ($this->cli) {
103
			$results = $this->analyze_route_path($this->path);
104
		} else {
105
			$Config             = Config::instance();
106
			$this->mirror_index = $this->determine_current_mirror_index($Config);
1 ignored issue
show
Documentation Bug introduced by
It seems like $this->determine_current_mirror_index($Config) can also be of type string. However, the property $mirror_index is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
107
			/**
108
			 * If match was not found - mirror is not allowed!
109
			 */
110
			if ($this->mirror_index === -1) {
111
				throw new ExitException("Mirror $this->host not allowed", 400);
112
			}
113
			$results = $this->analyze_route_path($this->path);
114
			$this->handle_redirect($Config, $results['path_normalized']);
115
		}
116
		$this->route = $results['route'];
117
		/**
118
		 * Separate numeric and other parts of route
119
		 */
120
		foreach ($this->route as $item) {
121
			if (is_numeric($item)) {
122
				$this->route_ids[] = $item;
123
			} else {
124
				$this->route_path[] = $item;
125
			}
126
		}
127
		$this->path_normalized = $results['path_normalized'];
128
		$this->cli_path        = $results['cli_path'];
129
		$this->admin_path      = $results['admin_path'];
130
		$this->api_path        = $results['api_path'];
131
		$this->current_module  = $results['current_module'];
132
		$this->home_page       = $results['home_page'];
133
	}
134
	/**
135
	 * @param Config $Config
136
	 *
137
	 * @return int
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
138
	 */
139
	protected function determine_current_mirror_index ($Config) {
140
		/**
141
		 * Search for url matching in all mirrors
142
		 */
143
		foreach ($Config->core['url'] as $i => $address) {
144
			list($scheme, $urls) = explode('://', $address, 2);
145
			if ($scheme == $this->scheme) {
146
				foreach (explode(';', $urls) as $url) {
147
					if (mb_strpos("$this->host/$this->path", "$url/") === 0) {
148
						return $i;
149
					}
150
				}
151
			}
152
		}
153
		return -1;
154
	}
155
	/**
156
	 * Process raw relative route.
157
	 *
158
	 * As result returns current route in system in form of array, normalized path, detects module path points to, whether this is API call, administration
159
	 * page, or home page whether this is API call, admin page, or home page
160
	 *
161
	 * @param string $path
162
	 *
163
	 * @return array Array contains next elements: `route`, `path_normalized`, `cli_path`, `admin_path`, `api_path`, `current_module`, `home_page`
164
	 */
165
	function analyze_route_path ($path) {
166
		$rc = trim($path, '/');
167
		if (Language::instance()->url_language($rc)) {
168
			$rc = explode('/', $rc, 2);
169
			$rc = isset($rc[1]) ? $rc[1] : '';
170
		}
171
		Event::instance()->fire(
172
			'System/Request/routing_replace',
173
			[
174
				'rc' => &$rc
175
			]
176
		);
177
		/**
178
		 * Obtaining page path in form of array
179
		 */
180
		$rc         = $rc ? explode('/', $rc) : [];
181
		$cli_path   = '';
182
		$admin_path = '';
183
		$api_path   = '';
184
		$home_page  = false;
185
		/**
186
		 * If url is cli, admin or API page - set corresponding variables to corresponding path prefix
187
		 */
188
		if (@mb_strtolower($rc[0]) == 'cli') {
189
			$cli_path = 'cli/';
190
			array_shift($rc);
191
		} elseif (@mb_strtolower($rc[0]) == 'admin') {
192
			$admin_path = 'admin/';
193
			array_shift($rc);
194
		} elseif (@mb_strtolower($rc[0]) == 'api') {
195
			$api_path = 'api/';
196
			array_shift($rc);
197
		}
198
		/**
199
		 * Module detection
200
		 */
201
		$current_module = $this->determine_page_module($rc, $home_page, $cli_path, $admin_path, $api_path);
202
		return [
203
			'route'           => $rc,
204
			'path_normalized' => trim(
205
				$cli_path.$admin_path.$api_path.$current_module.'/'.implode('/', $rc),
206
				'/'
207
			),
208
			'cli_path'        => (bool)$cli_path,
209
			'admin_path'      => (bool)$admin_path,
210
			'api_path'        => (bool)$api_path,
211
			'current_module'  => $current_module,
212
			'home_page'       => $home_page
213
		];
214
	}
215
	/**
216
	 * @param Config $Config
217
	 * @param string $path_normalized
218
	 *
219
	 * @throws ExitException
220
	 */
221
	protected function handle_redirect ($Config, $path_normalized) {
222
		/**
223
		 * Redirection processing
224
		 */
225
		if (strpos($path_normalized, 'System/redirect/') === 0) {
226
			if ($this->is_referer_local($Config)) {
227
				Response::instance()->redirect(
228
					substr($path_normalized, 16),
229
					301
230
				);
231
				throw new ExitException;
232
			} else {
233
				throw new ExitException(400);
234
			}
235
		}
236
	}
237
	/**
238
	 * Check whether referer is local
239
	 *
240
	 * @param Config $Config
241
	 *
242
	 * @return bool
243
	 */
244
	protected function is_referer_local ($Config) {
245
		$referer = $this->header('referer');
246
		if (!$referer) {
247
			return false;
248
		}
249
		list($referer_protocol, $referer_host) = explode('://', $referer);
250
		$referer_host = explode('/', $referer_host)[0];
251
		foreach ($Config->core['url'] as $address) {
252
			list($protocol, $urls) = explode('://', $address, 2);
253
			if ($protocol === $referer_protocol) {
254
				foreach (explode(';', $urls) as $url) {
255
					if (mb_strpos($referer_host, $url) === 0) {
256
						return true;
257
					}
258
				}
259
			}
260
		}
261
		return false;
262
	}
263
	/**
264
	 * Determine module of current page based on page path and system configuration
265
	 *
266
	 * @param array  $rc
267
	 * @param bool   $home_page
268
	 * @param string $cli_path
269
	 * @param string $admin_path
270
	 * @param string $api_path
271
	 *
272
	 * @return mixed|string
273
	 */
274
	protected function determine_page_module (&$rc, &$home_page, $cli_path, $admin_path, $api_path) {
275
		$Config  = Config::instance();
276
		$modules = $this->get_modules($Config, (bool)$admin_path);
277
		if (@in_array($rc[0], array_values($modules))) {
278
			return array_shift($rc);
279
		}
280
		if (@$modules[$rc[0]]) {
281
			return $modules[array_shift($rc)];
282
		}
283
		$current_module =
284
			$cli_path || $admin_path || $api_path || isset($rc[0])
285
				? 'System'
286
				: $Config->core['default_module'];
287
		if (!$cli_path && !$admin_path && !$api_path && !isset($rc[1])) {
288
			$home_page = true;
289
		}
290
		return $current_module;
291
	}
292
	/**
293
	 * Get array of modules
294
	 *
295
	 * @param Config $Config
296
	 * @param bool   $admin_path
297
	 *
298
	 * @return array Array of form [localized_module_name => module_name]
299
	 */
300
	protected function get_modules ($Config, $admin_path) {
301
		$modules = array_filter(
302
			$Config->components['modules'],
303
			function ($module_data) use ($admin_path) {
304
				/**
305
				 * Skip uninstalled modules and modules that are disabled (on all pages except admin pages)
306
				 */
307
				return
308
					(
309
						$admin_path &&
310
						$module_data['active'] == Config\Module_Properties::DISABLED
311
					) ||
312
					$module_data['active'] == Config\Module_Properties::ENABLED;
313
			}
314
		);
315
		$L       = Language::instance();
316
		foreach ($modules as $module => &$localized_name) {
317
			$localized_name = path($L->$module);
318
		}
319
		return array_flip($modules);
320
	}
321
	/**
322
	 * Get route part by index
323
	 *
324
	 * @param int $index
325
	 *
326
	 * @return int|null|string
327
	 */
328
	function route ($index) {
329
		return isset($this->route[$index]) ? $this->route[$index] : null;
330
	}
331
	/**
332
	 * Get route path part by index
333
	 *
334
	 * @param int $index
335
	 *
336
	 * @return null|string
337
	 */
338
	function route_path ($index) {
339
		return isset($this->route_path[$index]) ? $this->route_path[$index] : null;
340
	}
341
	/**
342
	 * Get route ids part by index
343
	 *
344
	 * @param int $index
345
	 *
346
	 * @return int|null
347
	 */
348
	function route_ids ($index) {
349
		return isset($this->route_ids[$index]) ? $this->route_ids[$index] : null;
350
	}
351
}
352