Completed
Push — master ( 32af77...22792e )
by Nazar
04:43
created

Route::init_route()   C

Complexity

Conditions 9
Paths 20

Size

Total Lines 49
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 49
rs 5.7446
cc 9
eloc 31
nc 20
nop 0
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 = -1;
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->admin_path     = false;
88
		$this->api_path       = false;
89
		$this->current_module = '';
90
		$this->home_page      = false;
91
		$Config               = Config::instance();
92
		/**
93
		 * Search for url matching in all mirrors
94
		 */
95
		foreach ($Config->core['url'] as $i => $address) {
96
			list($schema, $urls) = explode('://', $address, 2);
97
			if (
98
				$this->mirror_index === -1 &&
99
				$schema == $this->schema
100
			) {
101
				foreach (explode(';', $urls) as $url) {
102
					if (mb_strpos("$this->host/$this->path", "$url/") === 0) {
103
						$this->mirror_index = $i;
1 ignored issue
show
Documentation Bug introduced by
It seems like $i 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...
104
						break 2;
105
					}
106
				}
107
			}
108
		}
109
		unset($address, $i, $urls, $url, $schema);
110
		/**
111
		 * If match was not found - mirror is not allowed!
112
		 */
113
		if ($this->mirror_index === -1) {
114
			throw new ExitException("Mirror $this->host not allowed", 400);
115
		}
116
		$results = $this->analyze_route_path($this->path);
117
		$this->handle_redirect($Config, $results['path_normalized']);
118
		$this->route = $results['route'];
119
		/**
120
		 * Separate numeric and other parts of route
121
		 */
122
		foreach ($this->route as $item) {
123
			if (is_numeric($item)) {
124
				$this->route_ids[] = $item;
125
			} else {
126
				$this->route_path[] = $item;
127
			}
128
		}
129
		$this->path_normalized = $results['path_normalized'];
130
		$this->admin_path      = $results['admin_path'];
131
		$this->api_path        = $results['api_path'];
132
		$this->current_module  = $results['current_module'];
133
		$this->home_page       = $results['home_page'];
134
	}
135
	/**
136
	 * Process raw relative route.
137
	 *
138
	 * As result returns current route in system in form of array, corrected page address, detects MODULE, that responsible for processing this url,
139
	 * whether this is API call, admin page, or home page
140
	 *
141
	 * @param string $path
142
	 *
143
	 * @return false|string[] Array contains next elements: `route`, `path_normalized`, `admin_path`, `api_path`, `current_module`, `home_page`
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

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...
144
	 */
145
	function analyze_route_path ($path) {
146
		$rc = trim($path, '/');
147
		if (Language::instance()->url_language($rc)) {
148
			$rc = explode('/', $rc, 2);
149
			$rc = isset($rc[1]) ? $rc[1] : '';
150
		}
151
		Event::instance()->fire(
152
			'System/Route/routing_replace',
153
			[
154
				'rc' => &$rc
155
			]
156
		);
157
		Event::instance()->fire(
158
			'System/Request/routing_replace',
159
			[
160
				'rc' => &$rc
161
			]
162
		);
163
		/**
164
		 * Obtaining page path in form of array
165
		 */
166
		$rc         = $rc ? explode('/', $rc) : [];
167
		$admin_path = '';
168
		$api_path   = '';
169
		$home_page  = false;
170
		/**
171
		 * If url is admin or API page - set corresponding variables to corresponding path prefix
172
		 */
173
		if (@mb_strtolower($rc[0]) == 'admin') {
174
			$admin_path = 'admin/';
175
			array_shift($rc);
176
		} elseif (@mb_strtolower($rc[0]) == 'api') {
177
			$api_path = 'api/';
178
			array_shift($rc);
179
		}
180
		/**
181
		 * Module detection
182
		 */
183
		$current_module = $this->determine_page_module($rc, $home_page, $admin_path, $api_path);
184
		return [
185
			'route'           => $rc,
186
			'path_normalized' => trim(
187
				$admin_path.$api_path.$current_module.'/'.implode('/', $rc),
188
				'/'
189
			),
190
			'admin_path'      => (bool)$admin_path,
191
			'api_path'        => (bool)$api_path,
192
			'current_module'  => $current_module,
193
			'home_page'       => $home_page
194
		];
195
	}
196
	/**
197
	 * @param Config $Config
198
	 * @param string $path_normalized
199
	 *
200
	 * @throws ExitException
201
	 */
202
	protected function handle_redirect ($Config, $path_normalized) {
203
		/**
204
		 * Redirection processing
205
		 */
206
		if (strpos($path_normalized, 'System/redirect/') === 0) {
207
			if ($this->is_referer_local($Config)) {
208
				Response::instance()->redirect(
209
					substr($path_normalized, 16),
210
					301
211
				);
212
				throw new ExitException;
213
			} else {
214
				throw new ExitException(400);
215
			}
216
		}
217
	}
218
	/**
219
	 * Check whether referer is local
220
	 *
221
	 * @param Config $Config
222
	 *
223
	 * @return bool
224
	 */
225
	protected function is_referer_local ($Config) {
226
		if (!$this->referer) {
227
			return false;
228
		}
229
		list($referer_protocol, $referer_host) = explode('://', $this->referer);
230
		$referer_host = explode('/', $referer_host)[0];
231
		foreach ($Config->core['url'] as $address) {
232
			list($protocol, $urls) = explode('://', $address, 2);
233
			if ($protocol === $referer_protocol) {
234
				foreach (explode(';', $urls) as $url) {
235
					if (mb_strpos($referer_host, $url) === 0) {
236
						return true;
237
					}
238
				}
239
			}
240
		}
241
		return false;
242
	}
243
	/**
244
	 * Determine module of current page based on page path and system configuration
245
	 *
246
	 * @param array  $rc
247
	 * @param bool   $home_page
248
	 * @param string $admin_path
249
	 * @param string $api_path
250
	 *
251
	 * @return mixed|string
252
	 */
253
	protected function determine_page_module (&$rc, &$home_page, $admin_path, $api_path) {
254
		$Config  = Config::instance();
255
		$modules = $this->get_modules($Config, $admin_path);
0 ignored issues
show
Documentation introduced by
$admin_path is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
256
		if (@in_array($rc[0], array_values($modules))) {
257
			return array_shift($rc);
258
		}
259
		if (@$modules[$rc[0]]) {
260
			return $modules[array_shift($rc)];
261
		}
262
		$current_module =
263
			$admin_path || $api_path || isset($rc[0])
264
				? 'System'
265
				: $Config->core['default_module'];
266
		if (!$admin_path && !$api_path && !isset($rc[1])) {
267
			$home_page = true;
268
		}
269
		return $current_module;
270
	}
271
	/**
272
	 * Get array of modules
273
	 *
274
	 * @param Config $Config
275
	 * @param bool   $admin_path
276
	 *
277
	 * @return array Array of form [localized_module_name => module_name]
278
	 */
279
	protected function get_modules ($Config, $admin_path) {
280
		$modules = array_filter(
281
			$Config->components['modules'],
282
			function ($module_data) use ($admin_path) {
283
				/**
284
				 * Skip uninstalled modules and modules that are disabled (on all pages except admin pages)
285
				 */
286
				return
287
					(
288
						$admin_path &&
289
						$module_data['active'] == Config\Module_Properties::DISABLED
290
					) ||
291
					$module_data['active'] == Config\Module_Properties::ENABLED;
292
			}
293
		);
294
		$L       = Language::instance();
295
		foreach ($modules as $module => &$localized_name) {
296
			$localized_name = path($L->$module);
297
		}
298
		return array_flip($modules);
299
	}
300
}
301