1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Elgg; |
4
|
|
|
|
5
|
|
|
use Elgg\Http\Request; |
6
|
|
|
use Elgg\Http\ResponseBuilder; |
7
|
|
|
use RuntimeException; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* Delegates requests to controllers based on the registered configuration. |
11
|
|
|
* |
12
|
|
|
* Plugin devs should use these wrapper functions: |
13
|
|
|
* * elgg_register_page_handler |
14
|
|
|
* * elgg_unregister_page_handler |
15
|
|
|
* |
16
|
|
|
* @package Elgg.Core |
17
|
|
|
* @subpackage Router |
18
|
|
|
* @since 1.9.0 |
19
|
|
|
* @access private |
20
|
|
|
*/ |
21
|
|
|
class Router { |
22
|
|
|
|
23
|
|
|
use Profilable; |
24
|
|
|
|
25
|
|
|
private $handlers = array(); |
26
|
|
|
private $hooks; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Constructor |
30
|
|
|
* |
31
|
|
|
* @param PluginHooksService $hooks For customized routing. |
32
|
|
|
*/ |
33
|
77 |
|
public function __construct(PluginHooksService $hooks) { |
34
|
77 |
|
$this->hooks = $hooks; |
35
|
77 |
|
} |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Routes the request to a registered page handler |
39
|
|
|
* |
40
|
|
|
* This function triggers a plugin hook `'route', $identifier` so that plugins can |
41
|
|
|
* modify the routing or handle a request. |
42
|
|
|
* |
43
|
|
|
* @param Request $request The request to handle. |
44
|
|
|
* @return boolean Whether the request was routed successfully. |
45
|
|
|
* @access private |
46
|
|
|
*/ |
47
|
75 |
|
public function route(Request $request) { |
48
|
75 |
|
$segments = $request->getUrlSegments(); |
49
|
75 |
|
if ($segments) { |
50
|
75 |
|
$identifier = array_shift($segments); |
51
|
75 |
|
} else { |
52
|
|
|
$identifier = ''; |
53
|
|
|
$segments = []; |
54
|
|
|
} |
55
|
|
|
|
56
|
75 |
|
$is_walled_garden = _elgg_services()->config->get('walled_garden'); |
57
|
75 |
|
$is_logged_in = _elgg_services()->session->isLoggedIn(); |
58
|
75 |
|
$url = elgg_normalize_url($identifier . '/' . implode('/', $segments)); |
59
|
|
|
|
60
|
75 |
|
if ($is_walled_garden && !$is_logged_in && !$this->isPublicPage($url)) { |
61
|
1 |
|
if (!elgg_is_xhr()) { |
62
|
1 |
|
_elgg_services()->session->set('last_forward_from', current_page_url()); |
63
|
1 |
|
} |
64
|
1 |
|
register_error(_elgg_services()->translator->translate('loggedinrequired')); |
65
|
1 |
|
_elgg_services()->responseFactory->redirect('', 'walled_garden'); |
66
|
1 |
|
return false; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
// return false to stop processing the request (because you handled it) |
70
|
|
|
// return a new $result array if you want to route the request differently |
71
|
|
|
$old = array( |
72
|
74 |
|
'identifier' => $identifier, |
73
|
74 |
|
'handler' => $identifier, // backward compatibility |
74
|
74 |
|
'segments' => $segments, |
75
|
74 |
|
); |
76
|
|
|
|
77
|
74 |
|
if ($this->timer) { |
78
|
|
|
$this->timer->begin(['build page']); |
79
|
|
|
} |
80
|
|
|
|
81
|
74 |
|
ob_start(); |
82
|
|
|
|
83
|
74 |
|
$result = $this->hooks->trigger('route', $identifier, $old, $old); |
84
|
74 |
|
if ($result === false) { |
85
|
9 |
|
$output = ob_get_clean(); |
86
|
9 |
|
$response = elgg_ok_response($output); |
87
|
9 |
|
} else { |
88
|
65 |
|
if ($result !== $old) { |
89
|
2 |
|
_elgg_services()->logger->warn('Use the route:rewrite hook to modify routes.'); |
90
|
2 |
|
} |
91
|
|
|
|
92
|
65 |
|
if ($identifier != $result['identifier']) { |
93
|
1 |
|
$identifier = $result['identifier']; |
94
|
65 |
|
} else if ($identifier != $result['handler']) { |
95
|
1 |
|
$identifier = $result['handler']; |
96
|
1 |
|
} |
97
|
|
|
|
98
|
65 |
|
$segments = $result['segments']; |
99
|
|
|
|
100
|
65 |
|
$response = false; |
101
|
|
|
|
102
|
65 |
|
if (isset($this->handlers[$identifier]) && is_callable($this->handlers[$identifier])) { |
103
|
64 |
|
$function = $this->handlers[$identifier]; |
104
|
64 |
|
$response = call_user_func($function, $segments, $identifier); |
105
|
64 |
|
} |
106
|
|
|
|
107
|
65 |
|
$output = ob_get_clean(); |
108
|
|
|
|
109
|
65 |
|
if ($response === false) { |
110
|
1 |
|
return headers_sent(); |
111
|
|
|
} |
112
|
|
|
|
113
|
64 |
|
if (!$response instanceof ResponseBuilder) { |
114
|
4 |
|
$response = elgg_ok_response($output); |
115
|
4 |
|
} |
116
|
|
|
} |
117
|
|
|
|
118
|
73 |
|
if (_elgg_services()->responseFactory->getSentResponse()) { |
119
|
14 |
|
return true; |
120
|
|
|
} |
121
|
|
|
|
122
|
59 |
|
_elgg_services()->responseFactory->respond($response); |
123
|
58 |
|
return headers_sent(); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Register a function that gets called when the first part of a URL is |
128
|
|
|
* equal to the identifier. |
129
|
|
|
* |
130
|
|
|
* @param string $identifier The page type to handle |
131
|
|
|
* @param string $function Your function name |
132
|
|
|
* |
133
|
|
|
* @return bool Depending on success |
134
|
|
|
*/ |
135
|
199 |
|
public function registerPageHandler($identifier, $function) { |
136
|
199 |
|
if (is_callable($function, true)) { |
137
|
199 |
|
$this->handlers[$identifier] = $function; |
138
|
199 |
|
return true; |
139
|
|
|
} |
140
|
|
|
|
141
|
1 |
|
return false; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Unregister a page handler for an identifier |
146
|
|
|
* |
147
|
|
|
* @param string $identifier The page type identifier |
148
|
|
|
* |
149
|
|
|
* @return void |
150
|
|
|
*/ |
151
|
1 |
|
public function unregisterPageHandler($identifier) { |
152
|
1 |
|
unset($this->handlers[$identifier]); |
153
|
1 |
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Get page handlers as array of identifier => callback |
157
|
|
|
* |
158
|
|
|
* @return array |
159
|
|
|
*/ |
160
|
1 |
|
public function getPageHandlers() { |
161
|
1 |
|
return $this->handlers; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Filter a request through the route:rewrite hook |
166
|
|
|
* |
167
|
|
|
* @param Request $request Elgg request |
168
|
|
|
* |
169
|
|
|
* @return Request |
170
|
|
|
* @access private |
171
|
|
|
*/ |
172
|
1 |
|
public function allowRewrite(Request $request) { |
173
|
1 |
|
$segments = $request->getUrlSegments(); |
174
|
1 |
|
if ($segments) { |
175
|
1 |
|
$identifier = array_shift($segments); |
176
|
1 |
|
} else { |
177
|
|
|
$identifier = ''; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
$old = array( |
181
|
1 |
|
'identifier' => $identifier, |
182
|
1 |
|
'segments' => $segments, |
183
|
1 |
|
); |
184
|
1 |
|
$new = _elgg_services()->hooks->trigger('route:rewrite', $identifier, $old, $old); |
185
|
1 |
|
if ($new === $old) { |
186
|
|
|
return $request; |
187
|
|
|
} |
188
|
|
|
|
189
|
1 |
|
if (!isset($new['identifier']) || !isset($new['segments']) || !is_string($new['identifier']) || !is_array($new['segments']) |
190
|
1 |
|
) { |
191
|
|
|
throw new RuntimeException('rewrite_path handler returned invalid route data.'); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
// rewrite request |
195
|
1 |
|
$segments = $new['segments']; |
196
|
1 |
|
array_unshift($segments, $new['identifier']); |
197
|
1 |
|
return $request->setUrlSegments($segments); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Checks if the page should be allowed to be served in a walled garden mode |
202
|
|
|
* |
203
|
|
|
* Pages are registered to be public by {@elgg_plugin_hook public_pages walled_garden}. |
204
|
|
|
* |
205
|
|
|
* @param string $url Defaults to the current URL |
206
|
|
|
* |
207
|
|
|
* @return bool |
208
|
|
|
* @since 3.0 |
209
|
|
|
*/ |
210
|
22 |
|
public function isPublicPage($url = '') { |
211
|
22 |
|
if (empty($url)) { |
212
|
|
|
$url = current_page_url(); |
213
|
|
|
} |
214
|
|
|
|
215
|
22 |
|
$parts = parse_url($url); |
216
|
22 |
|
unset($parts['query']); |
217
|
22 |
|
unset($parts['fragment']); |
218
|
22 |
|
$url = elgg_http_build_url($parts); |
|
|
|
|
219
|
22 |
|
$url = rtrim($url, '/') . '/'; |
220
|
|
|
|
221
|
22 |
|
$site_url = _elgg_services()->config->getSiteUrl(); |
222
|
|
|
|
223
|
22 |
|
if ($url == $site_url) { |
224
|
|
|
// always allow index page |
225
|
|
|
return true; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
// default public pages |
229
|
|
|
$defaults = array( |
230
|
22 |
|
'walled_garden/.*', |
231
|
22 |
|
'action/.*', |
232
|
22 |
|
'login', |
233
|
22 |
|
'register', |
234
|
22 |
|
'forgotpassword', |
235
|
22 |
|
'changepassword', |
236
|
22 |
|
'refresh_token', |
237
|
22 |
|
'ajax/view/languages.js', |
238
|
22 |
|
'upgrade\.php', |
239
|
22 |
|
'css/.*', |
240
|
22 |
|
'js/.*', |
241
|
22 |
|
'cache/[0-9]+/\w+/.*', |
242
|
22 |
|
'cron/.*', |
243
|
22 |
|
'services/.*', |
244
|
22 |
|
'serve-file/.*', |
245
|
22 |
|
'robots.txt', |
246
|
22 |
|
'favicon.ico', |
247
|
22 |
|
); |
248
|
|
|
|
249
|
|
|
$params = [ |
250
|
22 |
|
'url' => $url, |
251
|
22 |
|
]; |
252
|
|
|
|
253
|
22 |
|
$public_routes = _elgg_services()->hooks->trigger('public_pages', 'walled_garden', $params, $defaults); |
254
|
|
|
|
255
|
22 |
|
$site_url = preg_quote($site_url); |
256
|
22 |
|
foreach ($public_routes as $public_route) { |
257
|
22 |
|
$pattern = "`^{$site_url}{$public_route}/*$`i"; |
258
|
22 |
|
if (preg_match($pattern, $url)) { |
259
|
19 |
|
return true; |
260
|
|
|
} |
261
|
21 |
|
} |
262
|
|
|
|
263
|
|
|
// non-public page |
264
|
3 |
|
return false; |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
} |
268
|
|
|
|
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.