Commit | Line | Data |
---|---|---|
d71e5a29 AE |
1 | <?php |
2 | namespace wcf\system\request; | |
d2864e2b | 3 | use wcf\system\event\EventHandler; |
6b51ca9c | 4 | use wcf\system\exception\SystemException; |
d71e5a29 | 5 | use wcf\system\SingletonFactory; |
3dcfb497 | 6 | use wcf\util\FileUtil; |
d71e5a29 AE |
7 | |
8 | /** | |
9 | * Handles routes for HTTP requests. | |
10 | * | |
11 | * Inspired by routing mechanism used by ASP.NET MVC and released under the terms of | |
12 | * the Microsoft Public License (MS-PL) http://www.opensource.org/licenses/ms-pl.html | |
13 | * | |
0c166126 | 14 | * @author Alexander Ebert |
ca4ba303 | 15 | * @copyright 2001-2014 WoltLab GmbH |
d71e5a29 AE |
16 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
17 | * @package com.woltlab.wcf | |
18 | * @subpackage system.request | |
9f959ced | 19 | * @category Community Framework |
d71e5a29 AE |
20 | */ |
21 | class RouteHandler extends SingletonFactory { | |
3dcfb497 AE |
22 | /** |
23 | * current host and protocol | |
24 | * @var string | |
25 | */ | |
26 | protected static $host = ''; | |
27 | ||
28 | /** | |
29 | * current absolute path | |
30 | * @var string | |
31 | */ | |
32 | protected static $path = ''; | |
33 | ||
b42a1cd9 AE |
34 | /** |
35 | * current path info component | |
36 | * @var string | |
37 | */ | |
6d61eb12 | 38 | protected static $pathInfo = null; |
b42a1cd9 | 39 | |
96a01a11 AE |
40 | /** |
41 | * HTTP protocol, either 'http://' or 'https://' | |
42 | * @var string | |
43 | */ | |
44 | protected static $protocol = ''; | |
45 | ||
46 | /** | |
47 | * HTTP encryption | |
48 | * @var boolean | |
49 | */ | |
50 | protected static $secure = null; | |
51 | ||
b07d3301 AE |
52 | /** |
53 | * true, if default controller is used (support for custom landing page) | |
54 | * @var boolean | |
55 | */ | |
56 | protected $isDefaultController = false; | |
57 | ||
d71e5a29 AE |
58 | /** |
59 | * list of available routes | |
0ad90fc3 | 60 | * @var array<\wcf\system\request\Route> |
d71e5a29 AE |
61 | */ |
62 | protected $routes = array(); | |
63 | ||
64 | /** | |
65 | * parsed route data | |
66 | * @var array | |
67 | */ | |
68 | protected $routeData = null; | |
69 | ||
70 | /** | |
0ad90fc3 | 71 | * @see \wcf\system\SingletonFactory::init() |
d71e5a29 AE |
72 | */ |
73 | protected function init() { | |
74 | $this->addDefaultRoutes(); | |
d2864e2b AE |
75 | |
76 | // fire event | |
77 | EventHandler::getInstance()->fireAction($this, 'didInit'); | |
d71e5a29 AE |
78 | } |
79 | ||
80 | /** | |
81 | * Adds default routes. | |
82 | */ | |
83 | protected function addDefaultRoutes() { | |
84 | $acpRoute = new Route('ACP_default', true); | |
85 | $acpRoute->setSchema('/{controller}/{id}'); | |
86 | $acpRoute->setParameterOption('controller', 'Index', null, true); | |
87 | $acpRoute->setParameterOption('id', null, '\d+', true); | |
88 | $this->addRoute($acpRoute); | |
89 | ||
90 | $defaultRoute = new Route('default'); | |
91 | $defaultRoute->setSchema('/{controller}/{id}'); | |
5bded211 | 92 | $defaultRoute->setParameterOption('controller', null, null, true); |
d71e5a29 AE |
93 | $defaultRoute->setParameterOption('id', null, '\d+', true); |
94 | $this->addRoute($defaultRoute); | |
95 | } | |
96 | ||
97 | /** | |
98 | * Adds a new route to the beginning of all routes. | |
99 | * | |
0ad90fc3 | 100 | * @param \wcf\system\request\Route $route |
d71e5a29 AE |
101 | */ |
102 | public function addRoute(Route $route) { | |
103 | array_unshift($this->routes, $route); | |
104 | } | |
105 | ||
106 | /** | |
28410a97 | 107 | * Returns true if a route matches. Please bear in mind, that the |
d71e5a29 AE |
108 | * first route which is able to consume all path components is used, |
109 | * even if other routes may fit better. Route order is crucial! | |
110 | * | |
d71e5a29 AE |
111 | * @return boolean |
112 | */ | |
3f9b6a95 | 113 | public function matches() { |
d71e5a29 | 114 | foreach ($this->routes as $route) { |
3f9b6a95 | 115 | if (RequestHandler::getInstance()->isACPRequest() != $route->isACP()) { |
d71e5a29 AE |
116 | continue; |
117 | } | |
118 | ||
b42a1cd9 | 119 | if ($route->matches(self::getPathInfo())) { |
d71e5a29 | 120 | $this->routeData = $route->getRouteData(); |
b07d3301 AE |
121 | |
122 | $this->isDefaultController = $this->routeData['isDefaultController']; | |
123 | unset($this->routeData['isDefaultController']); | |
124 | ||
d71e5a29 AE |
125 | $this->registerRouteData(); |
126 | return true; | |
127 | } | |
128 | } | |
129 | ||
130 | return false; | |
131 | } | |
132 | ||
b07d3301 | 133 | /** |
28410a97 | 134 | * Returns true if route uses default controller. |
b07d3301 AE |
135 | * |
136 | * @return boolean | |
137 | */ | |
138 | public function isDefaultController() { | |
139 | return $this->isDefaultController; | |
140 | } | |
141 | ||
d71e5a29 AE |
142 | /** |
143 | * Returns parsed route data | |
144 | * | |
145 | * @return array | |
146 | */ | |
147 | public function getRouteData() { | |
148 | return $this->routeData; | |
149 | } | |
150 | ||
151 | /** | |
152 | * Registers route data within $_GET and $_REQUEST. | |
d726f13d | 153 | */ |
d71e5a29 AE |
154 | protected function registerRouteData() { |
155 | foreach ($this->routeData as $key => $value) { | |
156 | $_GET[$key] = $value; | |
157 | $_REQUEST[$key] = $value; | |
158 | } | |
159 | } | |
f98e7cf2 AE |
160 | |
161 | /** | |
162 | * Builds a route based upon route components, this is nothing | |
163 | * but a reverse lookup. | |
164 | * | |
165 | * @param array $components | |
bc344403 | 166 | * @param boolean $isACP |
f98e7cf2 AE |
167 | * @return string |
168 | */ | |
bc344403 TD |
169 | public function buildRoute(array $components, $isACP = null) { |
170 | if ($isACP === null) $isACP = RequestHandler::getInstance()->isACPRequest(); | |
171 | ||
f98e7cf2 | 172 | foreach ($this->routes as $route) { |
bc344403 | 173 | if ($isACP != $route->isACP()) { |
f98e7cf2 AE |
174 | continue; |
175 | } | |
176 | ||
177 | if ($route->canHandle($components)) { | |
178 | return $route->buildLink($components); | |
179 | } | |
180 | } | |
181 | ||
182 | throw new SystemException("Unable to build route, no available route is satisfied."); | |
183 | } | |
3dcfb497 | 184 | |
96a01a11 | 185 | /** |
28410a97 | 186 | * Returns true if this is a secure connection. |
96a01a11 AE |
187 | * |
188 | * @return true | |
189 | */ | |
190 | public static function secureConnection() { | |
191 | if (self::$secure === null) { | |
192 | self::$secure = false; | |
193 | ||
3f2e2b27 | 194 | if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443 || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) { |
96a01a11 AE |
195 | self::$secure = true; |
196 | } | |
197 | } | |
198 | ||
199 | return self::$secure; | |
200 | } | |
201 | ||
202 | /** | |
203 | * Returns HTTP protocol, either 'http://' or 'https://'. | |
204 | * | |
205 | * @return string | |
206 | */ | |
207 | public static function getProtocol() { | |
208 | if (empty(self::$protocol)) { | |
209 | self::$protocol = 'http' . (self::secureConnection() ? 's' : '') . '://'; | |
210 | } | |
211 | ||
212 | return self::$protocol; | |
213 | } | |
214 | ||
3dcfb497 AE |
215 | /** |
216 | * Returns protocol and domain name. | |
217 | * | |
218 | * @return string | |
219 | */ | |
220 | public static function getHost() { | |
221 | if (empty(self::$host)) { | |
96a01a11 | 222 | self::$host = self::getProtocol() . $_SERVER['HTTP_HOST']; |
3dcfb497 AE |
223 | } |
224 | ||
225 | return self::$host; | |
226 | } | |
227 | ||
228 | /** | |
229 | * Returns absolute domain path. | |
230 | * | |
231 | * @param array $removeComponents | |
232 | * @return string | |
233 | */ | |
234 | public static function getPath(array $removeComponents = array()) { | |
235 | if (empty(self::$path)) { | |
236 | self::$path = FileUtil::addTrailingSlash(dirname($_SERVER['SCRIPT_NAME'])); | |
237 | } | |
238 | ||
239 | if (!empty($removeComponents)) { | |
240 | $path = explode('/', self::$path); | |
241 | foreach ($path as $index => $component) { | |
242 | if (empty($path[$index])) { | |
243 | unset($path[$index]); | |
244 | } | |
245 | ||
246 | if (in_array($component, $removeComponents)) { | |
247 | unset($path[$index]); | |
248 | } | |
249 | } | |
250 | ||
40e65021 | 251 | return '/' . implode('/', $path) . '/'; |
3dcfb497 AE |
252 | } |
253 | ||
254 | return self::$path; | |
255 | } | |
b42a1cd9 AE |
256 | |
257 | /** | |
258 | * Returns current path info component. | |
259 | * | |
260 | * @return string | |
261 | */ | |
262 | public static function getPathInfo() { | |
6d61eb12 AE |
263 | if (self::$pathInfo === null) { |
264 | self::$pathInfo = ''; | |
265 | ||
266 | // WCF 2.0: index.php/Foo/Bar/ | |
267 | if (URL_LEGACY_MODE) { | |
268 | if (isset($_SERVER['PATH_INFO'])) { | |
269 | self::$pathInfo = $_SERVER['PATH_INFO']; | |
270 | } | |
271 | else if (isset($_SERVER['ORIG_PATH_INFO'])) { | |
272 | self::$pathInfo = $_SERVER['ORIG_PATH_INFO']; | |
273 | ||
274 | // in some configurations ORIG_PATH_INFO contains the path to the file | |
275 | // if the intended PATH_INFO component is empty | |
276 | if (!empty(self::$pathInfo)) { | |
277 | if (isset($_SERVER['SCRIPT_NAME']) && (self::$pathInfo == $_SERVER['SCRIPT_NAME'])) { | |
278 | self::$pathInfo = ''; | |
279 | } | |
280 | ||
281 | if (isset($_SERVER['PHP_SELF']) && (self::$pathInfo == $_SERVER['PHP_SELF'])) { | |
282 | self::$pathInfo = ''; | |
283 | } | |
284 | ||
285 | if (isset($_SERVER['SCRIPT_URL']) && (self::$pathInfo == $_SERVER['SCRIPT_URL'])) { | |
286 | self::$pathInfo = ''; | |
287 | } | |
288 | } | |
289 | } | |
a10a3e5b | 290 | } |
6d61eb12 AE |
291 | else { |
292 | // WCF 2.1: ?Foo/Bar/ | |
293 | if (!empty($_SERVER['QUERY_STRING'])) { | |
294 | $pos = mb_strpos($_SERVER['QUERY_STRING'], '&'); | |
295 | $route = ''; | |
296 | if ($pos === false) { | |
297 | $route = $_SERVER['QUERY_STRING']; | |
b42a1cd9 | 298 | } |
6d61eb12 AE |
299 | else { |
300 | $route = mb_substr($_SERVER['QUERY_STRING'], 0, $pos); | |
b42a1cd9 | 301 | } |
2d63c13c | 302 | |
6d61eb12 AE |
303 | if (mb_strpos($route, '=') === false) { |
304 | self::$pathInfo = $route; | |
b42a1cd9 AE |
305 | } |
306 | } | |
307 | } | |
b42a1cd9 AE |
308 | } |
309 | ||
310 | return self::$pathInfo; | |
311 | } | |
d71e5a29 | 312 | } |