Completed
Push — master ( 830834...005b3d )
by Thomas
10:38
created

Router::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 14
nc 4
nop 1
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Bernhard Posselt <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Jörn Friedrich Dreyer <[email protected]>
7
 * @author Lukas Reschke <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Robin McCorkell <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Vincent Petry <[email protected]>
14
 *
15
 * @copyright Copyright (c) 2016, ownCloud GmbH.
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC\Route;
33
34
use OCP\ILogger;
35
use OCP\Route\IRouter;
36
use OCP\AppFramework\App;
37
use OCP\Util;
38
use Symfony\Component\Routing\Exception\RouteNotFoundException;
39
use Symfony\Component\Routing\Matcher\UrlMatcher;
40
use Symfony\Component\Routing\Generator\UrlGenerator;
41
use Symfony\Component\Routing\RequestContext;
42
use Symfony\Component\Routing\RouteCollection;
43
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
44
45
class Router implements IRouter {
0 ignored issues
show
Deprecated Code introduced by
The interface OCP\Route\IRouter has been deprecated with message: 9.0.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
46
	/** @var RouteCollection[] */
47
	protected $collections = [];
48
	/** @var null|RouteCollection */
49
	protected $collection = null;
50
	/** @var null|string */
51
	protected $collectionName = null;
52
	/** @var null|RouteCollection */
53
	protected $root = null;
54
	/** @var null|UrlGenerator */
55
	protected $generator = null;
56
	/** @var string[] */
57
	protected $routingFiles;
58
	/** @var bool */
59
	protected $loaded = false;
60
	/** @var array */
61
	protected $loadedApps = [];
62
	/** @var ILogger */
63
	protected $logger;
64
	/** @var RequestContext */
65
	protected $context;
66
67
	/**
68
	 * @param ILogger $logger
69
	 */
70
	public function __construct(ILogger $logger) {
71
		$this->logger = $logger;
72
		$baseUrl = \OC::$WEBROOT;
73
		if(!(getenv('front_controller_active') === 'true')) {
74
			$baseUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php');
75
		}
76
		if (!\OC::$CLI) {
77
			$method = $_SERVER['REQUEST_METHOD'];
78
		} else {
79
			$method = 'GET';
80
		}
81
		$request = \OC::$server->getRequest();
82
		$host = $request->getServerHost();
83
		$schema = $request->getServerProtocol();
84
		$this->context = new RequestContext($baseUrl, $method, $host, $schema);
85
		// TODO cache
86
		$this->root = $this->getCollection('root');
87
	}
88
89
	/**
90
	 * Get the files to load the routes from
91
	 *
92
	 * @return string[]
93
	 */
94
	public function getRoutingFiles() {
95
		if (!isset($this->routingFiles)) {
96
			$this->routingFiles = [];
97 View Code Duplication
			foreach (\OC_APP::getEnabledApps() as $app) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
98
				$appPath = \OC_App::getAppPath($app);
99
				if($appPath !== false) {
100
					$file = $appPath . '/appinfo/routes.php';
101
					if (file_exists($file)) {
102
						$this->routingFiles[$app] = $file;
103
					}
104
				}
105
			}
106
		}
107
		return $this->routingFiles;
108
	}
109
110
	/**
111
	 * Loads the routes
112
	 *
113
	 * @param null|string $app
114
	 */
115
	public function loadRoutes($app = null) {
116
		if(is_string($app)) {
117
			$app = \OC_App::cleanAppId($app);
118
		}
119
120
		$requestedApp = $app;
121
		if ($this->loaded) {
122
			return;
123
		}
124
		if (is_null($app)) {
125
			$this->loaded = true;
126
			$routingFiles = $this->getRoutingFiles();
127
		} else {
128
			if (isset($this->loadedApps[$app])) {
129
				return;
130
			}
131
			$file = \OC_App::getAppPath($app) . '/appinfo/routes.php';
132
			if ($file !== false && file_exists($file)) {
133
				$routingFiles = [$app => $file];
134
			} else {
135
				$routingFiles = [];
136
			}
137
		}
138
		\OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes');
139
		foreach ($routingFiles as $app => $file) {
140
			if (!isset($this->loadedApps[$app])) {
141
				if (!\OC_App::isAppLoaded($app)) {
142
					// app MUST be loaded before app routes
143
					// try again next time loadRoutes() is called
144
					$this->loaded = false;
145
					continue;
146
				}
147
				$this->loadedApps[$app] = true;
148
				$this->useCollection($app);
149
				$this->requireRouteFile($file, $app);
150
				$collection = $this->getCollection($app);
151
				$collection->addPrefix('/apps/' . $app);
152
				$this->root->addCollection($collection);
153
			}
154
		}
155
		if (!isset($this->loadedApps['core'])) {
156
			$this->loadedApps['core'] = true;
157
			$this->useCollection('root');
158
			require_once __DIR__ . '/../../../settings/routes.php';
159
			require_once __DIR__ . '/../../../core/routes.php';
160
		}
161
		if ($this->loaded) {
162
			// include ocs routes, must be loaded last for /ocs prefix
163
			require_once __DIR__ . '/../../../ocs/routes.php';
164
			$collection = $this->getCollection('ocs');
165
			$collection->addPrefix('/ocs');
166
			$this->root->addCollection($collection);
167
		}
168
		\OC::$server->getEventLogger()->end('loadroutes' . $requestedApp);
169
	}
170
171
	/**
172
	 * @return string
173
	 * @deprecated
174
	 */
175
	public function getCacheKey() {
176
		return '';
177
	}
178
179
	/**
180
	 * @param string $name
181
	 * @return \Symfony\Component\Routing\RouteCollection
182
	 */
183
	protected function getCollection($name) {
184
		if (!isset($this->collections[$name])) {
185
			$this->collections[$name] = new RouteCollection();
186
		}
187
		return $this->collections[$name];
188
	}
189
190
	/**
191
	 * Sets the collection to use for adding routes
192
	 *
193
	 * @param string $name Name of the collection to use.
194
	 * @return void
195
	 */
196
	public function useCollection($name) {
197
		$this->collection = $this->getCollection($name);
198
		$this->collectionName = $name;
199
	}
200
201
	/**
202
	 * returns the current collection name in use for adding routes
203
	 *
204
	 * @return string the collection name
205
	 */
206
	public function getCurrentCollection() {
207
		return $this->collectionName;
208
	}
209
210
211
	/**
212
	 * Create a \OC\Route\Route.
213
	 *
214
	 * @param string $name Name of the route to create.
215
	 * @param string $pattern The pattern to match
216
	 * @param array $defaults An array of default parameter values
217
	 * @param array $requirements An array of requirements for parameters (regexes)
218
	 * @return \OC\Route\Route
219
	 */
220
	public function create($name,
221
						   $pattern,
222
						   array $defaults = [],
223
						   array $requirements = []) {
224
		$route = new Route($pattern, $defaults, $requirements);
225
		$this->collection->add($name, $route);
226
		return $route;
227
	}
228
229
	/**
230
	 * Find the route matching $url
231
	 *
232
	 * @param string $url The url to find
233
	 * @throws \Exception
234
	 * @return void
235
	 */
236
	public function match($url) {
237
		if (substr($url, 0, 6) === '/apps/') {
238
			// empty string / 'apps' / $app / rest of the route
239
			list(, , $app,) = explode('/', $url, 4);
240
241
			$app = \OC_App::cleanAppId($app);
242
			\OC::$REQUESTEDAPP = $app;
243
			$this->loadRoutes($app);
244
		} else if (substr($url, 0, 6) === '/core/' or substr($url, 0, 10) === '/settings/') {
245
			\OC::$REQUESTEDAPP = $url;
246
			if (!\OC::$server->getConfig()->getSystemValue('maintenance', false) && !Util::needUpgrade()) {
247
				\OC_App::loadApps();
248
			}
249
			$this->loadRoutes('core');
250
		} else {
251
			$this->loadRoutes();
252
		}
253
254
		$matcher = new UrlMatcher($this->root, $this->context);
0 ignored issues
show
Bug introduced by
It seems like $this->root can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
255
		try {
256
			$parameters = $matcher->match($url);
257
		} catch (ResourceNotFoundException $e) {
258
			if (substr($url, -1) !== '/') {
259
				// We allow links to apps/files? for backwards compatibility reasons
260
				// However, since Symfony does not allow empty route names, the route
261
				// we need to match is '/', so we need to append the '/' here.
262
				try {
263
					$parameters = $matcher->match($url . '/');
264
				} catch (ResourceNotFoundException $newException) {
265
					// If we still didn't match a route, we throw the original exception
266
					throw $e;
267
				}
268
			} else {
269
				throw $e;
270
			}
271
		}
272
273
		\OC::$server->getEventLogger()->start('run_route', 'Run route');
274
		if (isset($parameters['action'])) {
275
			$action = $parameters['action'];
276
			if (!is_callable($action)) {
277
				throw new \Exception('not a callable action');
278
			}
279
			unset($parameters['action']);
280
			call_user_func($action, $parameters);
281
		} elseif (isset($parameters['file'])) {
282
			include $parameters['file'];
283
		} else {
284
			throw new \Exception('no action available');
285
		}
286
		\OC::$server->getEventLogger()->end('run_route');
287
	}
288
289
	/**
290
	 * Get the url generator
291
	 *
292
	 * @return \Symfony\Component\Routing\Generator\UrlGenerator
293
	 *
294
	 */
295
	public function getGenerator() {
296
		if (null !== $this->generator) {
297
			return $this->generator;
298
		}
299
300
		return $this->generator = new UrlGenerator($this->root, $this->context);
0 ignored issues
show
Bug introduced by
It seems like $this->root can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
301
	}
302
303
	/**
304
	 * Generate url based on $name and $parameters
305
	 *
306
	 * @param string $name Name of the route to use.
307
	 * @param array $parameters Parameters for the route
308
	 * @param bool $absolute
309
	 * @return string
310
	 */
311
	public function generate($name,
312
							 $parameters = [],
313
							 $absolute = false) {
314
		$this->loadRoutes();
315
		try {
316
			$referenceType = UrlGenerator::ABSOLUTE_URL;
317
			if ($absolute === false) {
318
				$referenceType = UrlGenerator::ABSOLUTE_PATH;
319
			}
320
			return $this->getGenerator()->generate($name, $parameters, $referenceType);
321
		} catch (RouteNotFoundException $e) {
322
			$this->logger->logException($e);
323
			return '';
324
		}
325
	}
326
327
	/**
328
	 * To isolate the variable scope used inside the $file it is required in it's own method
329
	 *
330
	 * @param string $file the route file location to include
331
	 * @param string $appName
332
	 */
333
	private function requireRouteFile($file, $appName) {
334
		$this->setupRoutes(include_once $file, $appName);
335
	}
336
337
338
	/**
339
	 * If a routes.php file returns an array, try to set up the application and
340
	 * register the routes for the app. The application class will be chosen by
341
	 * camelcasing the appname, e.g.: my_app will be turned into
342
	 * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
343
	 * App will be intialized. This makes it optional to ship an
344
	 * appinfo/application.php by using the built in query resolver
345
	 *
346
	 * @param array $routes the application routes
347
	 * @param string $appName the name of the app.
348
	 */
349
	private function setupRoutes($routes, $appName) {
350
		if (is_array($routes)) {
351
			$appNameSpace = App::buildAppNamespace($appName);
352
353
			$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
354
355
			if (class_exists($applicationClassName)) {
356
				$application = new $applicationClassName();
357
			} else {
358
				$application = new App($appName);
359
			}
360
361
			$application->registerRoutes($this, $routes);
362
		}
363
	}
364
}
365