|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* @package CleverStyle Framework |
|
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\Request\Route\Static_files, |
|
15
|
|
|
cs\Response; |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* @property bool $cli |
|
19
|
|
|
* @property string $scheme |
|
20
|
|
|
* @property string $host |
|
21
|
|
|
* @property string $path |
|
22
|
|
|
* |
|
23
|
|
|
* @method string header(string $name) |
|
24
|
|
|
*/ |
|
25
|
|
|
trait Route { |
|
26
|
|
|
use |
|
27
|
|
|
Static_files; |
|
28
|
|
|
/** |
|
29
|
|
|
* Current mirror according to configuration |
|
30
|
|
|
* |
|
31
|
|
|
* @var int |
|
32
|
|
|
*/ |
|
33
|
|
|
public $mirror_index; |
|
34
|
|
|
/** |
|
35
|
|
|
* Normalized processed representation of relative address, may differ from raw, should be used in most cases |
|
36
|
|
|
* |
|
37
|
|
|
* @var string |
|
38
|
|
|
*/ |
|
39
|
|
|
public $path_normalized; |
|
40
|
|
|
/** |
|
41
|
|
|
* Contains parsed route of current page url in form of array without module name and prefixes `admin|api` |
|
42
|
|
|
* |
|
43
|
|
|
* @var array |
|
44
|
|
|
*/ |
|
45
|
|
|
public $route; |
|
46
|
|
|
/** |
|
47
|
|
|
* Like `$route` property, but excludes numerical items |
|
48
|
|
|
* |
|
49
|
|
|
* @var string[] |
|
50
|
|
|
*/ |
|
51
|
|
|
public $route_path; |
|
52
|
|
|
/** |
|
53
|
|
|
* Like `$route` property, but only includes numerical items (opposite to route_path property) |
|
54
|
|
|
* |
|
55
|
|
|
* @var int[] |
|
56
|
|
|
*/ |
|
57
|
|
|
public $route_ids; |
|
58
|
|
|
/** |
|
59
|
|
|
* Request to administration section |
|
60
|
|
|
* |
|
61
|
|
|
* @var bool |
|
62
|
|
|
*/ |
|
63
|
|
|
public $admin_path; |
|
64
|
|
|
/** |
|
65
|
|
|
* Request to CLI interface |
|
66
|
|
|
* |
|
67
|
|
|
* @var bool |
|
68
|
|
|
*/ |
|
69
|
|
|
public $cli_path; |
|
70
|
|
|
/** |
|
71
|
|
|
* Request to api section |
|
72
|
|
|
* |
|
73
|
|
|
* @var bool |
|
74
|
|
|
*/ |
|
75
|
|
|
public $api_path; |
|
76
|
|
|
/** |
|
77
|
|
|
* Request to regular page (not administration section, not API and not CLI) |
|
78
|
|
|
* |
|
79
|
|
|
* @var bool |
|
80
|
|
|
*/ |
|
81
|
|
|
public $regular_path; |
|
82
|
|
|
/** |
|
83
|
|
|
* Current module |
|
84
|
|
|
* |
|
85
|
|
|
* @var string |
|
86
|
|
|
*/ |
|
87
|
|
|
public $current_module; |
|
88
|
|
|
/** |
|
89
|
|
|
* Home page |
|
90
|
|
|
* |
|
91
|
|
|
* @var bool |
|
92
|
|
|
*/ |
|
93
|
|
|
public $home_page; |
|
94
|
|
|
/** |
|
95
|
|
|
* Initialize route based on system configuration, requires `::init_server()` being called first since uses its data |
|
96
|
|
|
* |
|
97
|
|
|
* @throws ExitException |
|
98
|
|
|
*/ |
|
99
|
30 |
|
public function init_route () { |
|
100
|
|
|
/** |
|
101
|
|
|
* Serve static files here for early exit |
|
102
|
|
|
*/ |
|
103
|
30 |
|
$this->serve_static_files(); |
|
104
|
30 |
|
$this->mirror_index = -1; |
|
105
|
30 |
|
$this->path_normalized = ''; |
|
106
|
30 |
|
$this->route = []; |
|
107
|
30 |
|
$this->route_path = []; |
|
108
|
30 |
|
$this->route_ids = []; |
|
109
|
30 |
|
$this->cli_path = false; |
|
110
|
30 |
|
$this->admin_path = false; |
|
111
|
30 |
|
$this->api_path = false; |
|
112
|
30 |
|
$this->regular_path = true; |
|
113
|
30 |
|
$this->current_module = ''; |
|
114
|
30 |
|
$this->home_page = false; |
|
115
|
30 |
|
if ($this->cli) { |
|
116
|
4 |
|
$results = $this->analyze_route_path($this->path); |
|
117
|
|
|
} else { |
|
118
|
28 |
|
$Config = Config::instance(); |
|
119
|
28 |
|
$this->mirror_index = $this->determine_current_mirror_index($Config); |
|
120
|
|
|
/** |
|
121
|
|
|
* If match was not found - mirror is not allowed! |
|
122
|
|
|
*/ |
|
123
|
28 |
|
if ($this->mirror_index === -1) { |
|
124
|
2 |
|
throw new ExitException("Mirror $this->host not allowed", 400); |
|
125
|
|
|
} |
|
126
|
28 |
|
$results = $this->analyze_route_path($this->path); |
|
127
|
28 |
|
$this->handle_redirect($Config, $results['path_normalized']); |
|
128
|
|
|
} |
|
129
|
28 |
|
$this->route = $results['route']; |
|
130
|
28 |
|
$this->route_path = $results['route_path']; |
|
131
|
28 |
|
$this->route_ids = $results['route_ids']; |
|
132
|
28 |
|
$this->path_normalized = $results['path_normalized']; |
|
133
|
28 |
|
$this->cli_path = $results['cli_path']; |
|
134
|
28 |
|
$this->admin_path = $results['admin_path']; |
|
135
|
28 |
|
$this->api_path = $results['api_path']; |
|
136
|
28 |
|
$this->regular_path = $results['regular_path']; |
|
137
|
28 |
|
$this->current_module = $results['current_module']; |
|
138
|
28 |
|
$this->home_page = $results['home_page']; |
|
139
|
28 |
|
} |
|
140
|
|
|
/** |
|
141
|
|
|
* @param Config $Config |
|
142
|
|
|
* |
|
143
|
|
|
* @return int |
|
144
|
|
|
*/ |
|
145
|
28 |
|
protected function determine_current_mirror_index ($Config) { |
|
146
|
|
|
/** |
|
147
|
|
|
* Search for url matching in all mirrors |
|
148
|
|
|
*/ |
|
149
|
28 |
|
foreach ($Config->core['url'] as $i => $address) { |
|
150
|
28 |
|
list($scheme, $urls) = explode('://', $address, 2); |
|
151
|
28 |
|
if ($scheme == $this->scheme) { |
|
152
|
28 |
|
foreach (explode(';', $urls) as $url) { |
|
153
|
28 |
|
if (mb_strpos("$this->host/$this->path", "$url/") === 0) { |
|
154
|
28 |
|
return $i; |
|
155
|
|
|
} |
|
156
|
|
|
} |
|
157
|
|
|
} |
|
158
|
|
|
} |
|
159
|
2 |
|
return -1; |
|
160
|
|
|
} |
|
161
|
|
|
/** |
|
162
|
|
|
* Process raw relative route. |
|
163
|
|
|
* |
|
164
|
|
|
* As result returns current route in system in form of array, normalized path, detects module path points to, whether this is API call, administration |
|
165
|
|
|
* page, or home page whether this is API call, admin page, or home page |
|
166
|
|
|
* |
|
167
|
|
|
* @param string $path |
|
168
|
|
|
* |
|
169
|
|
|
* @return array Array contains next elements: `route`, `path_normalized`, `cli_path`, `admin_path`, `api_path`, `current_module`, `home_page` |
|
170
|
|
|
*/ |
|
171
|
30 |
|
public function analyze_route_path ($path) { |
|
172
|
30 |
|
$route = trim($path, '/'); |
|
173
|
30 |
|
Event::instance()->fire( |
|
174
|
30 |
|
'System/Request/routing_replace/before', |
|
175
|
|
|
[ |
|
176
|
30 |
|
'rc' => &$route |
|
177
|
|
|
] |
|
178
|
|
|
); |
|
179
|
28 |
|
$url_language = Language::instance()->url_language($route); |
|
180
|
|
|
/** |
|
181
|
|
|
* Obtaining page path in form of array |
|
182
|
|
|
*/ |
|
183
|
28 |
|
$route = explode('/', $route); |
|
184
|
28 |
|
if ($url_language) { |
|
185
|
4 |
|
array_shift($route); |
|
186
|
|
|
} |
|
187
|
28 |
|
if (@$route[0] === '') { |
|
188
|
22 |
|
array_shift($route); |
|
189
|
|
|
} |
|
190
|
28 |
|
$cli_path = $this->cli && @strtolower($route[0]) == 'cli'; |
|
191
|
28 |
|
$admin_path = @strtolower($route[0]) == 'admin'; |
|
192
|
28 |
|
$api_path = @strtolower($route[0]) == 'api'; |
|
193
|
28 |
|
$regular_path = !($cli_path || $admin_path || $api_path); |
|
194
|
28 |
|
$path_prefix = ''; |
|
195
|
28 |
|
$home_page = false; |
|
196
|
|
|
/** |
|
197
|
|
|
* If url is cli, admin or API page - set corresponding variables to corresponding path prefix |
|
198
|
|
|
*/ |
|
199
|
28 |
|
if (!$regular_path) { |
|
200
|
8 |
|
$path_prefix = array_shift($route).'/'; |
|
201
|
|
|
} |
|
202
|
|
|
/** |
|
203
|
|
|
* Module detection |
|
204
|
|
|
*/ |
|
205
|
28 |
|
$current_module = $this->determine_page_module($route, $home_page, $admin_path, $regular_path); |
|
206
|
28 |
|
list($route_path, $route_ids) = $this->split_route($route); |
|
207
|
28 |
|
$rc = implode('/', $route); |
|
208
|
28 |
|
$old_rc = $rc; |
|
209
|
28 |
|
$old_route = $route; |
|
210
|
28 |
|
$old_route_path = $route_path; |
|
211
|
28 |
|
$old_route_ids = $route_ids; |
|
212
|
28 |
|
Event::instance()->fire( |
|
213
|
28 |
|
'System/Request/routing_replace/after', |
|
214
|
|
|
[ |
|
215
|
28 |
|
'rc' => &$rc, // TODO: Deprecated key, remove in 6.x |
|
216
|
28 |
|
'route' => &$route, |
|
217
|
28 |
|
'route_path' => &$route_path, |
|
218
|
28 |
|
'route_ids' => &$route_ids, |
|
219
|
28 |
|
'cli_path' => &$cli_path, |
|
220
|
28 |
|
'admin_path' => &$admin_path, |
|
221
|
28 |
|
'api_path' => &$api_path, |
|
222
|
28 |
|
'regular_path' => &$regular_path, |
|
223
|
28 |
|
'current_module' => &$current_module, |
|
224
|
28 |
|
'home_page' => &$home_page |
|
225
|
|
|
] |
|
226
|
|
|
); |
|
227
|
|
|
// TODO: Deprecated, remove in 6.x |
|
228
|
28 |
|
if ($rc != $old_rc) { |
|
229
|
|
|
$route = explode('/', $rc); |
|
230
|
|
|
list($route_path, $route_ids) = $this->split_route($route); |
|
231
|
|
|
} |
|
232
|
28 |
|
if ($route != $old_route && $route_path == $old_route_path && $route_ids == $old_route_ids) { |
|
233
|
2 |
|
list($route_path, $route_ids) = $this->split_route($route); |
|
234
|
|
|
} |
|
235
|
|
|
return [ |
|
236
|
28 |
|
'route' => $route, |
|
237
|
28 |
|
'route_path' => $route_path, |
|
238
|
28 |
|
'route_ids' => $route_ids, |
|
239
|
28 |
|
'path_normalized' => trim( |
|
240
|
28 |
|
"$path_prefix$current_module/$rc", |
|
241
|
28 |
|
'/' |
|
242
|
|
|
), |
|
243
|
28 |
|
'cli_path' => $cli_path, |
|
244
|
28 |
|
'admin_path' => $admin_path, |
|
245
|
28 |
|
'api_path' => $api_path, |
|
246
|
28 |
|
'regular_path' => $regular_path, |
|
247
|
28 |
|
'current_module' => $current_module, |
|
248
|
28 |
|
'home_page' => $home_page |
|
249
|
|
|
]; |
|
250
|
|
|
} |
|
251
|
|
|
/** |
|
252
|
|
|
* @param array $route |
|
253
|
|
|
* |
|
254
|
|
|
* @return array[] Key `0` contains array of paths, key `1` contains array of identifiers |
|
255
|
|
|
*/ |
|
256
|
28 |
|
protected function split_route ($route) { |
|
257
|
28 |
|
$route_path = []; |
|
258
|
28 |
|
$route_ids = []; |
|
259
|
|
|
/** |
|
260
|
|
|
* Separate numeric and other parts of route |
|
261
|
|
|
*/ |
|
262
|
28 |
|
foreach ($route as $item) { |
|
263
|
6 |
|
if (is_numeric($item)) { |
|
264
|
2 |
|
$route_ids[] = $item; |
|
265
|
|
|
} else { |
|
266
|
6 |
|
$route_path[] = $item; |
|
267
|
|
|
} |
|
268
|
|
|
} |
|
269
|
28 |
|
return [$route_path, $route_ids]; |
|
270
|
|
|
} |
|
271
|
|
|
/** |
|
272
|
|
|
* @param Config $Config |
|
273
|
|
|
* @param string $path_normalized |
|
274
|
|
|
* |
|
275
|
|
|
* @throws ExitException |
|
276
|
|
|
*/ |
|
277
|
28 |
|
protected function handle_redirect ($Config, $path_normalized) { |
|
278
|
|
|
/** |
|
279
|
|
|
* Redirection processing |
|
280
|
|
|
*/ |
|
281
|
28 |
|
if (strpos($path_normalized, 'System/redirect/') === 0) { |
|
282
|
2 |
|
if ($this->is_referer_local($Config)) { |
|
283
|
2 |
|
Response::instance()->redirect( |
|
284
|
2 |
|
substr($path_normalized, 16), |
|
285
|
2 |
|
301 |
|
286
|
|
|
); |
|
287
|
2 |
|
throw new ExitException; |
|
288
|
|
|
} else { |
|
289
|
2 |
|
throw new ExitException(400); |
|
290
|
|
|
} |
|
291
|
|
|
} |
|
292
|
28 |
|
} |
|
293
|
|
|
/** |
|
294
|
|
|
* Check whether referer is local |
|
295
|
|
|
* |
|
296
|
|
|
* @param Config $Config |
|
297
|
|
|
* |
|
298
|
|
|
* @return bool |
|
299
|
|
|
*/ |
|
300
|
2 |
|
protected function is_referer_local ($Config) { |
|
301
|
2 |
|
$referer = $this->header('referer'); |
|
302
|
2 |
|
if (!$referer) { |
|
303
|
2 |
|
return false; |
|
304
|
|
|
} |
|
305
|
2 |
|
list($referer_protocol, $referer_host) = explode('://', $referer); |
|
306
|
2 |
|
$referer_host = explode('/', $referer_host)[0]; |
|
307
|
2 |
|
foreach ($Config->core['url'] as $address) { |
|
308
|
2 |
|
list($protocol, $urls) = explode('://', $address, 2); |
|
309
|
2 |
|
if ($protocol === $referer_protocol) { |
|
310
|
2 |
|
foreach (explode(';', $urls) as $url) { |
|
311
|
2 |
|
if (mb_strpos("$referer_host/", "$url/") === 0) { |
|
312
|
2 |
|
return true; |
|
313
|
|
|
} |
|
314
|
|
|
} |
|
315
|
|
|
} |
|
316
|
|
|
} |
|
317
|
2 |
|
return false; |
|
318
|
|
|
} |
|
319
|
|
|
/** |
|
320
|
|
|
* Determine module of current page based on page path and system configuration |
|
321
|
|
|
* |
|
322
|
|
|
* @param array $rc |
|
323
|
|
|
* @param bool $home_page |
|
324
|
|
|
* @param bool $admin_path |
|
325
|
|
|
* @param bool $regular_path |
|
326
|
|
|
* |
|
327
|
|
|
* @return string |
|
328
|
|
|
*/ |
|
329
|
28 |
|
protected function determine_page_module (&$rc, &$home_page, $admin_path, $regular_path) { |
|
330
|
28 |
|
$Config = Config::instance(); |
|
331
|
28 |
|
$modules = $this->get_modules($Config, (bool)$admin_path); |
|
332
|
28 |
|
$module_specified = @$rc[0]; |
|
333
|
28 |
|
if ($module_specified) { |
|
334
|
6 |
|
if (in_array($module_specified, $modules)) { |
|
335
|
6 |
|
return array_shift($rc); |
|
336
|
|
|
} |
|
337
|
2 |
|
$L = Language::instance(); |
|
338
|
2 |
|
foreach ($modules as $module) { |
|
339
|
2 |
|
if ($module_specified == path($L->$module)) { |
|
340
|
2 |
|
array_shift($rc); |
|
341
|
2 |
|
return $module; |
|
342
|
|
|
} |
|
343
|
|
|
} |
|
344
|
|
|
} |
|
345
|
24 |
|
if (!$regular_path || $module_specified) { |
|
346
|
4 |
|
$current_module = 'System'; |
|
347
|
|
|
} else { |
|
348
|
22 |
|
$current_module = $Config->core['default_module']; |
|
349
|
22 |
|
$home_page = true; |
|
350
|
|
|
} |
|
351
|
24 |
|
return $current_module; |
|
352
|
|
|
} |
|
353
|
|
|
/** |
|
354
|
|
|
* Get array of modules |
|
355
|
|
|
* |
|
356
|
|
|
* @param Config $Config |
|
357
|
|
|
* @param bool $admin_path |
|
358
|
|
|
* |
|
359
|
|
|
* @return string[] |
|
360
|
|
|
*/ |
|
361
|
28 |
|
protected function get_modules ($Config, $admin_path) { |
|
362
|
28 |
|
$modules = array_filter( |
|
363
|
28 |
|
$Config->components['modules'], |
|
364
|
28 |
|
function ($module_data) use ($admin_path) { |
|
365
|
|
|
/** |
|
366
|
|
|
* Skip uninstalled modules and modules that are disabled (on all pages except admin pages) |
|
367
|
|
|
*/ |
|
368
|
|
|
return |
|
369
|
|
|
( |
|
370
|
28 |
|
$admin_path && |
|
371
|
4 |
|
$module_data['active'] == Config\Module_Properties::DISABLED |
|
372
|
|
|
) || |
|
373
|
28 |
|
$module_data['active'] == Config\Module_Properties::ENABLED; |
|
374
|
28 |
|
} |
|
375
|
|
|
); |
|
376
|
28 |
|
return array_keys($modules); |
|
377
|
|
|
} |
|
378
|
|
|
/** |
|
379
|
|
|
* Get route part by index |
|
380
|
|
|
* |
|
381
|
|
|
* @param int $index |
|
382
|
|
|
* |
|
383
|
|
|
* @return int|null|string |
|
384
|
|
|
*/ |
|
385
|
2 |
|
public function route ($index) { |
|
386
|
2 |
|
return @$this->route[$index]; |
|
387
|
|
|
} |
|
388
|
|
|
/** |
|
389
|
|
|
* Get route path part by index |
|
390
|
|
|
* |
|
391
|
|
|
* @param int $index |
|
392
|
|
|
* |
|
393
|
|
|
* @return null|string |
|
394
|
|
|
*/ |
|
395
|
2 |
|
public function route_path ($index) { |
|
396
|
2 |
|
return @$this->route_path[$index]; |
|
397
|
|
|
} |
|
398
|
|
|
/** |
|
399
|
|
|
* Get route ids part by index |
|
400
|
|
|
* |
|
401
|
|
|
* @param int $index |
|
402
|
|
|
* |
|
403
|
|
|
* @return int|null |
|
404
|
|
|
*/ |
|
405
|
2 |
|
public function route_ids ($index) { |
|
406
|
2 |
|
return @$this->route_ids[$index]; |
|
407
|
|
|
} |
|
408
|
|
|
} |
|
409
|
|
|
|