Changed URL structure to be no longer dependent on PATH_INFO
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / request / RouteHandler.class.php
CommitLineData
d71e5a29
AE
1<?php
2namespace wcf\system\request;
d2864e2b 3use wcf\system\event\EventHandler;
6b51ca9c 4use wcf\system\exception\SystemException;
d71e5a29 5use wcf\system\SingletonFactory;
3dcfb497 6use 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 */
21class 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}