Completed
Push — master ( a84fba...e8b4d4 )
by Thomas
11:12
created

Router::getGenerator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 7
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, $baseUrl = null) {
71
		$this->logger = $logger;
72
		if (is_null($baseUrl)) {
73
			$baseUrl = \OC::$WEBROOT;
74
		}
75
		if(!(getenv('front_controller_active') === 'true')) {
76
			$baseUrl = rtrim($baseUrl, '/') . '/index.php';
77
		}
78
		if (!\OC::$CLI) {
79
			$method = $_SERVER['REQUEST_METHOD'];
80
		} else {
81
			$method = 'GET';
82
		}
83
		$request = \OC::$server->getRequest();
84
		$host = $request->getServerHost();
85
		$schema = $request->getServerProtocol();
86
		$this->context = new RequestContext($baseUrl, $method, $host, $schema);
87
		// TODO cache
88
		$this->root = $this->getCollection('root');
89
	}
90
91
	/**
92
	 * Get the files to load the routes from
93
	 *
94
	 * @return string[]
95
	 */
96
	public function getRoutingFiles() {
97
		if (!isset($this->routingFiles)) {
98
			$this->routingFiles = [];
99 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...
100
				$appPath = \OC_App::getAppPath($app);
101
				if($appPath !== false) {
102
					$file = $appPath . '/appinfo/routes.php';
103
					if (file_exists($file)) {
104
						$this->routingFiles[$app] = $file;
105
					}
106
				}
107
			}
108
		}
109
		return $this->routingFiles;
110
	}
111
112
	/**
113
	 * Loads the routes
114
	 *
115
	 * @param null|string $app
116
	 */
117
	public function loadRoutes($app = null) {
118
		if(is_string($app)) {
119
			$app = \OC_App::cleanAppId($app);
120
		}
121
122
		$requestedApp = $app;
123
		if ($this->loaded) {
124
			return;
125
		}
126
		if (is_null($app)) {
127
			$this->loaded = true;
128
			$routingFiles = $this->getRoutingFiles();
129
		} else {
130
			if (isset($this->loadedApps[$app])) {
131
				return;
132
			}
133
			$file = \OC_App::getAppPath($app) . '/appinfo/routes.php';
134
			if ($file !== false && file_exists($file)) {
135
				$routingFiles = [$app => $file];
136
			} else {
137
				$routingFiles = [];
138
			}
139
		}
140
		\OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes');
141
		foreach ($routingFiles as $app => $file) {
142
			if (!isset($this->loadedApps[$app])) {
143
				if (!\OC_App::isAppLoaded($app)) {
144
					// app MUST be loaded before app routes
145
					// try again next time loadRoutes() is called
146
					$this->loaded = false;
147
					continue;
148
				}
149
				$this->loadedApps[$app] = true;
150
				$this->useCollection($app);
151
				$this->requireRouteFile($file, $app);
152
				$collection = $this->getCollection($app);
153
				$collection->addPrefix('/apps/' . $app);
154
				$this->root->addCollection($collection);
155
156
				// Also add the OCS collection
157
				$collection = $this->getCollection($app.'.ocs');
158
				$collection->addPrefix('/ocsapp/apps/' . $app);
159
				$this->root->addCollection($collection);
160
			}
161
		}
162
		if (!isset($this->loadedApps['core'])) {
163
			$this->loadedApps['core'] = true;
164
			$this->useCollection('root');
165
			require_once __DIR__ . '/../../../settings/routes.php';
166
			require_once __DIR__ . '/../../../core/routes.php';
167
		}
168
		if ($this->loaded) {
169
			// include ocs routes, must be loaded last for /ocs prefix
170
			require_once __DIR__ . '/../../../ocs/routes.php';
171
			$collection = $this->getCollection('ocs');
172
			$collection->addPrefix('/ocs');
173
			$this->root->addCollection($collection);
174
		}
175
		\OC::$server->getEventLogger()->end('loadroutes' . $requestedApp);
176
	}
177
178
	/**
179
	 * @return string
180
	 * @deprecated
181
	 */
182
	public function getCacheKey() {
183
		return '';
184
	}
185
186
	/**
187
	 * @param string $name
188
	 * @return \Symfony\Component\Routing\RouteCollection
189
	 */
190
	protected function getCollection($name) {
191
		if (!isset($this->collections[$name])) {
192
			$this->collections[$name] = new RouteCollection();
193
		}
194
		return $this->collections[$name];
195
	}
196
197
	/**
198
	 * Sets the collection to use for adding routes
199
	 *
200
	 * @param string $name Name of the collection to use.
201
	 * @return void
202
	 */
203
	public function useCollection($name) {
204
		$this->collection = $this->getCollection($name);
205
		$this->collectionName = $name;
206
	}
207
208
	/**
209
	 * returns the current collection name in use for adding routes
210
	 *
211
	 * @return string the collection name
212
	 */
213
	public function getCurrentCollection() {
214
		return $this->collectionName;
215
	}
216
217
218
	/**
219
	 * Create a \OC\Route\Route.
220
	 *
221
	 * @param string $name Name of the route to create.
222
	 * @param string $pattern The pattern to match
223
	 * @param array $defaults An array of default parameter values
224
	 * @param array $requirements An array of requirements for parameters (regexes)
225
	 * @return \OC\Route\Route
226
	 */
227
	public function create($name,
228
						   $pattern,
229
						   array $defaults = [],
230
						   array $requirements = []) {
231
		$route = new Route($pattern, $defaults, $requirements);
232
		$this->collection->add($name, $route);
233
		return $route;
234
	}
235
236
	/**
237
	 * Find the route matching $url
238
	 *
239
	 * @param string $url The url to find
240
	 * @throws \Exception
241
	 * @return void
242
	 */
243
	public function match($url) {
244
		if (substr($url, 0, 6) === '/apps/') {
245
			// empty string / 'apps' / $app / rest of the route
246
			list(, , $app,) = explode('/', $url, 4);
247
248
			$app = \OC_App::cleanAppId($app);
249
			\OC::$REQUESTEDAPP = $app;
250
			$this->loadRoutes($app);
251
		} else if (substr($url, 0, 13) === '/ocsapp/apps/') {
252
			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
253
			list(, , , $app,) = explode('/', $url, 5);
254
255
			$app = \OC_App::cleanAppId($app);
256
			\OC::$REQUESTEDAPP = $app;
257
			$this->loadRoutes($app);
258
		} else if (substr($url, 0, 6) === '/core/' or substr($url, 0, 10) === '/settings/') {
259
			\OC::$REQUESTEDAPP = $url;
260
			if (!\OC::$server->getConfig()->getSystemValue('maintenance', false) && !Util::needUpgrade()) {
261
				\OC_App::loadApps();
262
			}
263
			$this->loadRoutes('core');
264
		} else {
265
			$this->loadRoutes();
266
		}
267
268
		$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...
269
		try {
270
			$parameters = $matcher->match($url);
271
		} catch (ResourceNotFoundException $e) {
272
			if (substr($url, -1) !== '/') {
273
				// We allow links to apps/files? for backwards compatibility reasons
274
				// However, since Symfony does not allow empty route names, the route
275
				// we need to match is '/', so we need to append the '/' here.
276
				try {
277
					$parameters = $matcher->match($url . '/');
278
				} catch (ResourceNotFoundException $newException) {
279
					// If we still didn't match a route, we throw the original exception
280
					throw $e;
281
				}
282
			} else {
283
				throw $e;
284
			}
285
		}
286
287
		\OC::$server->getEventLogger()->start('run_route', 'Run route');
288
		if (isset($parameters['action'])) {
289
			$action = $parameters['action'];
290
			if (!is_callable($action)) {
291
				throw new \Exception('not a callable action');
292
			}
293
			unset($parameters['action']);
294
			call_user_func($action, $parameters);
295
		} elseif (isset($parameters['file'])) {
296
			include $parameters['file'];
297
		} else {
298
			throw new \Exception('no action available');
299
		}
300
		\OC::$server->getEventLogger()->end('run_route');
301
	}
302
303
	/**
304
	 * Get the url generator
305
	 *
306
	 * @return \Symfony\Component\Routing\Generator\UrlGenerator
307
	 *
308
	 */
309
	public function getGenerator() {
310
		if (null !== $this->generator) {
311
			return $this->generator;
312
		}
313
314
		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...
315
	}
316
317
	/**
318
	 * Generate url based on $name and $parameters
319
	 *
320
	 * @param string $name Name of the route to use.
321
	 * @param array $parameters Parameters for the route
322
	 * @param bool $absolute
323
	 * @return string
324
	 */
325
	public function generate($name,
326
							 $parameters = [],
327
							 $absolute = false) {
328
		$this->loadRoutes();
329
		try {
330
			$referenceType = UrlGenerator::ABSOLUTE_URL;
331
			if ($absolute === false) {
332
				$referenceType = UrlGenerator::ABSOLUTE_PATH;
333
			}
334
			return $this->getGenerator()->generate($name, $parameters, $referenceType);
335
		} catch (RouteNotFoundException $e) {
336
			$this->logger->logException($e);
337
			return '';
338
		}
339
	}
340
341
	/**
342
	 * To isolate the variable scope used inside the $file it is required in it's own method
343
	 *
344
	 * @param string $file the route file location to include
345
	 * @param string $appName
346
	 */
347
	private function requireRouteFile($file, $appName) {
348
		$this->setupRoutes(include_once $file, $appName);
349
	}
350
351
352
	/**
353
	 * If a routes.php file returns an array, try to set up the application and
354
	 * register the routes for the app. The application class will be chosen by
355
	 * camelcasing the appname, e.g.: my_app will be turned into
356
	 * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
357
	 * App will be intialized. This makes it optional to ship an
358
	 * appinfo/application.php by using the built in query resolver
359
	 *
360
	 * @param array $routes the application routes
361
	 * @param string $appName the name of the app.
362
	 */
363
	private function setupRoutes($routes, $appName) {
364
		if (is_array($routes)) {
365
			$appNameSpace = App::buildAppNamespace($appName);
366
367
			$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
368
369
			if (class_exists($applicationClassName)) {
370
				$application = new $applicationClassName();
371
			} else {
372
				$application = new App($appName);
373
			}
374
375
			$application->registerRoutes($this, $routes);
376
		}
377
	}
378
}
379