Completed
Push — master ( 04c2f3...b80779 )
by Nazar
04:15
created

Route::process_route()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 46
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 46
rs 8.4752
cc 6
eloc 29
nc 18
nop 1
1
<?php
2
/**
3
 * @package   CleverStyle CMS
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2015, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs;
9
/**
10
 * Provides next events:
11
 *  System/Route/routing_replace
12
 *  ['rc'    => &$rc] //Reference to string with current route, this string can be changed
13
 *
14
 * @method static Route instance($check = false)
15
 */
16
class Route {
17
	use
18
		Singleton;
19
	/**
20
	 * Current mirror according to configuration
21
	 *
22
	 * @var int
23
	 */
24
	public $mirror_index = -1;
25
	/**
26
	 * Relative address as it came from URL
27
	 *
28
	 * @var string
29
	 */
30
	public $raw_relative_address = '';
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 $relative_address = '';
37
	/**
38
	 * Contains parsed route of current page url in form of array without module name and prefixes <i>admin</i>/<i>api</i>
39
	 *
40
	 * @var array
41
	 */
42
	public $route = [];
43
	/**
44
	 * Like $route property, but excludes numerical items
45
	 *
46
	 * @var string[]
47
	 */
48
	public $path = [];
49
	/**
50
	 * Like $route property, but only includes numerical items (opposite to route_path property)
51
	 *
52
	 * @var int[]
53
	 */
54
	public $ids = [];
55
	/**
56
	 * Loading of configuration, initialization of $Config, $Cache, $L and Page objects, Routing processing
57
	 *
58
	 * @throws ExitException
59
	 */
60
	protected function construct () {
61
		$Config = Config::instance();
62
		/**
63
		 * @var _SERVER $_SERVER
64
		 */
65
		$this->raw_relative_address = urldecode(trim($_SERVER->request_uri, '/'));
66
		$this->raw_relative_address = null_byte_filter($this->raw_relative_address);
67
		/**
68
		 * Search for url matching in all mirrors
69
		 */
70
		foreach ($Config->core['url'] as $i => $address) {
71
			list($protocol, $urls) = explode('://', $address, 2);
72
			if (
73
				$this->mirror_index === -1 &&
74
				$protocol == $_SERVER->protocol
75
			) {
76
				foreach (explode(';', $urls) as $url) {
77
					if (mb_strpos("$_SERVER->host$this->raw_relative_address", $url) === 0) {
78
						$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...
79
						break 2;
80
					}
81
				}
82
			}
83
		}
84
		unset($address, $i, $urls, $url, $protocol);
85
		/**
86
		 * If match was not found - mirror is not allowed!
87
		 */
88
		if ($this->mirror_index === -1) {
89
			trigger_error("Mirror $_SERVER->host not allowed", E_USER_ERROR);
90
			throw new ExitException(400);
91
		}
92
		/**
93
		 * Remove trailing slashes
94
		 */
95
		$this->raw_relative_address = trim($this->raw_relative_address, ' /\\');
96
		$processed_route            = $this->process_route($this->raw_relative_address);
97
		/**
98
		 * Redirection processing
99
		 */
100
		if (mb_strpos($processed_route['relative_address'], 'System/redirect/') === 0) {
101
			if ($this->is_referer_local($Config)) {
102
				_header('Location: '.substr($processed_route['relative_address'], 16), true, 301);
103
				throw new ExitException(301);
104
			} else {
105
				throw new ExitException(400);
106
			}
107
		}
108
		if (!$processed_route) {
109
			throw new ExitException(403);
110
		}
111
		$this->route = $processed_route['route'];
112
		/**
113
		 * Separate numeric and other parts of route
114
		 */
115
		foreach ($this->route as $item) {
116
			if (is_numeric($item)) {
117
				$this->ids[] = $item;
118
			} else {
119
				$this->path[] = $item;
120
			}
121
		}
122
		$this->relative_address = $processed_route['relative_address'];
123
		admin_path($processed_route['ADMIN']);
1 ignored issue
show
Unused Code introduced by
The call to the function admin_path() seems unnecessary as the function has no side-effects.
Loading history...
124
		api_path($processed_route['API']);
1 ignored issue
show
Unused Code introduced by
The call to the function api_path() seems unnecessary as the function has no side-effects.
Loading history...
125
		current_module($processed_route['MODULE']);
1 ignored issue
show
Unused Code introduced by
The call to the function current_module() seems unnecessary as the function has no side-effects.
Loading history...
126
		home_page($processed_route['HOME']);
1 ignored issue
show
Unused Code introduced by
The call to the function home_page() seems unnecessary as the function has no side-effects.
Loading history...
127
	}
128
	/**
129
	 * Check whether referer is local
130
	 *
131
	 * @param Config $Config
132
	 *
133
	 * @return bool
134
	 */
135
	protected function is_referer_local ($Config) {
136
		/**
137
		 * @var _SERVER $_SERVER
138
		 */
139
		if (!$_SERVER->referer) {
140
			return false;
141
		}
142
		list($referer_protocol, $referer_host) = explode('://', $_SERVER->referer);
143
		$referer_host = explode('/', $referer_host)[0];
144
		foreach ($Config->core['url'] as $address) {
145
			list($protocol, $urls) = explode('://', $address, 2);
146
			if ($protocol === $referer_protocol) {
147
				foreach (explode(';', $urls) as $url) {
148
					if (mb_strpos($referer_host, $url) === 0) {
149
						return true;
150
					}
151
				}
152
			}
153
		}
154
		return false;
155
	}
156
	/**
157
	 * Process raw relative route.
158
	 *
159
	 * As result returns current route in system in form of array, corrected page address, detects MODULE, that responsible for processing this url,
160
	 * whether this is API call, ADMIN page, or HOME page
161
	 *
162
	 * @param string $raw_relative_address
163
	 *
164
	 * @return false|string[] Relative address or <i>false</i> if access denied (occurs when admin access is limited by IP). Array contains next elements:
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...
165
	 *                        route, relative_address, ADMIN, API, MODULE, HOME
166
	 */
167
	function process_route ($raw_relative_address) {
168
		$rc = explode('?', $raw_relative_address, 2)[0];
169
		$rc = trim($rc, '/');
170
		if (Language::instance()->url_language($rc)) {
171
			$rc = explode('/', $rc, 2);
172
			$rc = isset($rc[1]) ? $rc[1] : '';
173
		}
174
		Event::instance()->fire(
175
			'System/Route/routing_replace',
176
			[
177
				'rc' => &$rc
178
			]
179
		);
180
		/**
181
		 * Obtaining page path in form of array
182
		 */
183
		$rc    = $rc ? explode('/', $rc) : [];
184
		$ADMIN = '';
185
		$API   = '';
186
		$HOME  = false;
187
		/**
188
		 * If url is admin or API page - set corresponding variables to corresponding path prefix
189
		 */
190
		if (@mb_strtolower($rc[0]) == 'admin') {
191
			$ADMIN = 'admin/';
192
			array_shift($rc);
193
		} elseif (@mb_strtolower($rc[0]) == 'api') {
194
			$API = 'api/';
195
			array_shift($rc);
196
		}
197
		/**
198
		 * Module detection
199
		 */
200
		$MODULE = $this->determine_page_module($rc, $HOME, $ADMIN, $API);
201
		return [
202
			'route'            => $rc,
203
			'relative_address' => trim(
204
				$ADMIN.$API.$MODULE.'/'.implode('/', $rc),
205
				'/'
206
			),
207
			'ADMIN'            => (bool)$ADMIN,
208
			'API'              => (bool)$API,
209
			'MODULE'           => $MODULE,
210
			'HOME'             => $HOME
211
		];
212
	}
213
	/**
214
	 * Determine module of current page based on page path and system configuration
215
	 *
216
	 * @param array  $rc
217
	 * @param bool   $HOME
218
	 * @param string $ADMIN
219
	 * @param string $API
220
	 *
221
	 * @return mixed|string
222
	 */
223
	protected function determine_page_module (&$rc, &$HOME, $ADMIN, $API) {
224
		$Config  = Config::instance();
225
		$modules = $this->get_modules($Config, $ADMIN);
0 ignored issues
show
Documentation introduced by
$ADMIN 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...
226
		if (@in_array($rc[0], array_values($modules))) {
227
			return array_shift($rc);
228
		}
229
		if (@$modules[$rc[0]]) {
230
			return $modules[array_shift($rc)];
231
		}
232
		$MODULE =
233
			$ADMIN || $API || isset($rc[0])
234
				? 'System'
235
				: $Config->core['default_module'];
236
		if (!$ADMIN && !$API && !isset($rc[1])) {
237
			$HOME = true;
238
		}
239
		return $MODULE;
240
	}
241
	/**
242
	 * Get array of modules
243
	 *
244
	 * @param Config $Config
245
	 * @param bool   $ADMIN
246
	 *
247
	 * @return array Array of form [localized_module_name => module_name]
248
	 */
249
	protected function get_modules ($Config, $ADMIN) {
250
		$modules = array_filter(
251
			$Config->components['modules'],
252
			function ($module_data) use ($ADMIN) {
253
				/**
254
				 * Skip uninstalled modules and modules that are disabled (on all pages except admin pages)
255
				 */
256
				return
257
					(
258
						$ADMIN &&
259
						$module_data['active'] == Config\Module_Properties::DISABLED
260
					) ||
261
					$module_data['active'] == Config\Module_Properties::ENABLED;
262
			}
263
		);
264
		$L       = Language::instance();
265
		foreach ($modules as $module => &$localized_name) {
266
			$localized_name = path($L->$module);
267
		}
268
		return array_flip($modules);
269
	}
270
}
271