Passed
Push — master ( 54d36c...7a9211 )
by Roeland
12:34 queued 12s
created

Router   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 372
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 173
dl 0
loc 372
rs 3.52
c 0
b 0
f 0
wmc 61

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getRoutingFiles() 0 14 5
A __construct() 0 17 5
C loadRoutes() 0 60 12
A create() 0 7 1
A getCurrentCollection() 0 2 1
A getCacheKey() 0 2 1
A useCollection() 0 3 1
A getCollection() 0 5 2
A getGenerator() 0 6 2
A getApplicationClass() 0 12 2
A requireRouteFile() 0 2 1
C match() 0 65 14
A setupRoutes() 0 4 2
B fixLegacyRootName() 0 26 9
A generate() 0 14 3

How to fix   Complexity   

Complex Class

Complex classes like Router often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Router, and based on these observations, apply Extract Interface, too.

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

47
class Router implements /** @scrutinizer ignore-deprecated */ IRouter {

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

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

Loading history...
48
	/** @var RouteCollection[] */
49
	protected $collections = [];
50
	/** @var null|RouteCollection */
51
	protected $collection = null;
52
	/** @var null|string */
53
	protected $collectionName = null;
54
	/** @var null|RouteCollection */
55
	protected $root = null;
56
	/** @var null|UrlGenerator */
57
	protected $generator = null;
58
	/** @var string[] */
59
	protected $routingFiles;
60
	/** @var bool */
61
	protected $loaded = false;
62
	/** @var array */
63
	protected $loadedApps = [];
64
	/** @var ILogger */
65
	protected $logger;
66
	/** @var RequestContext */
67
	protected $context;
68
69
	/**
70
	 * @param ILogger $logger
71
	 */
72
	public function __construct(ILogger $logger) {
73
		$this->logger = $logger;
74
		$baseUrl = \OC::$WEBROOT;
75
		if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
76
			$baseUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php');
77
		}
78
		if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) {
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
			foreach (\OC_APP::getEnabledApps() as $app) {
100
				$appPath = \OC_App::getAppPath($app);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppPath() has been deprecated: 11.0.0 use \OC::$server->getAppManager()->getAppPath() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

100
				$appPath = /** @scrutinizer ignore-deprecated */ \OC_App::getAppPath($app);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

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

Loading history...
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';
0 ignored issues
show
Bug introduced by
Are you sure OC_App::getAppPath($app) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

133
			$file = /** @scrutinizer ignore-type */ \OC_App::getAppPath($app) . '/appinfo/routes.php';
Loading history...
Deprecated Code introduced by
The function OC_App::getAppPath() has been deprecated: 11.0.0 use \OC::$server->getAppManager()->getAppPath() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

133
			$file = /** @scrutinizer ignore-deprecated */ \OC_App::getAppPath($app) . '/appinfo/routes.php';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

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

Loading history...
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
				$this->root->addCollection($collection);
0 ignored issues
show
Bug introduced by
The method addCollection() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

153
				$this->root->/** @scrutinizer ignore-call */ 
154
                 addCollection($collection);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
154
155
				// Also add the OCS collection
156
				$collection = $this->getCollection($app.'.ocs');
157
				$collection->addPrefix('/ocsapp');
158
				$this->root->addCollection($collection);
159
			}
160
		}
161
		if (!isset($this->loadedApps['core'])) {
162
			$this->loadedApps['core'] = true;
163
			$this->useCollection('root');
164
			require_once __DIR__ . '/../../../core/routes.php';
165
166
			// Also add the OCS collection
167
			$collection = $this->getCollection('root.ocs');
168
			$collection->addPrefix('/ocsapp');
169
			$this->root->addCollection($collection);
170
		}
171
		if ($this->loaded) {
172
			$collection = $this->getCollection('ocs');
173
			$collection->addPrefix('/ocs');
174
			$this->root->addCollection($collection);
175
		}
176
		\OC::$server->getEventLogger()->end('loadroutes' . $requestedApp);
177
	}
178
179
	/**
180
	 * @return string
181
	 * @deprecated
182
	 */
183
	public function getCacheKey() {
184
		return '';
185
	}
186
187
	/**
188
	 * @param string $name
189
	 * @return \Symfony\Component\Routing\RouteCollection
190
	 */
191
	protected function getCollection($name) {
192
		if (!isset($this->collections[$name])) {
193
			$this->collections[$name] = new RouteCollection();
194
		}
195
		return $this->collections[$name];
196
	}
197
198
	/**
199
	 * Sets the collection to use for adding routes
200
	 *
201
	 * @param string $name Name of the collection to use.
202
	 * @return void
203
	 */
204
	public function useCollection($name) {
205
		$this->collection = $this->getCollection($name);
206
		$this->collectionName = $name;
207
	}
208
209
	/**
210
	 * returns the current collection name in use for adding routes
211
	 *
212
	 * @return string the collection name
213
	 */
214
	public function getCurrentCollection() {
215
		return $this->collectionName;
216
	}
217
218
219
	/**
220
	 * Create a \OC\Route\Route.
221
	 *
222
	 * @param string $name Name of the route to create.
223
	 * @param string $pattern The pattern to match
224
	 * @param array $defaults An array of default parameter values
225
	 * @param array $requirements An array of requirements for parameters (regexes)
226
	 * @return \OC\Route\Route
227
	 */
228
	public function create($name,
229
						   $pattern,
230
						   array $defaults = [],
231
						   array $requirements = []) {
232
		$route = new Route($pattern, $defaults, $requirements);
233
		$this->collection->add($name, $route);
0 ignored issues
show
Bug introduced by
The method add() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

233
		$this->collection->/** @scrutinizer ignore-call */ 
234
                     add($name, $route);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
234
		return $route;
235
	}
236
237
	/**
238
	 * Find the route matching $url
239
	 *
240
	 * @param string $url The url to find
241
	 * @throws \Exception
242
	 * @return void
243
	 */
244
	public function match($url) {
245
		if (substr($url, 0, 6) === '/apps/') {
246
			// empty string / 'apps' / $app / rest of the route
247
			list(, , $app,) = explode('/', $url, 4);
248
249
			$app = \OC_App::cleanAppId($app);
250
			\OC::$REQUESTEDAPP = $app;
251
			$this->loadRoutes($app);
252
		} elseif (substr($url, 0, 13) === '/ocsapp/apps/') {
253
			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
254
			list(, , , $app,) = explode('/', $url, 5);
255
256
			$app = \OC_App::cleanAppId($app);
257
			\OC::$REQUESTEDAPP = $app;
258
			$this->loadRoutes($app);
259
		} elseif (substr($url, 0, 10) === '/settings/') {
260
			$this->loadRoutes('settings');
261
		} elseif (substr($url, 0, 6) === '/core/') {
262
			\OC::$REQUESTEDAPP = $url;
263
			if (!\OC::$server->getConfig()->getSystemValueBool('maintenance') && !Util::needUpgrade()) {
264
				\OC_App::loadApps();
265
			}
266
			$this->loadRoutes('core');
267
		} else {
268
			$this->loadRoutes();
269
		}
270
271
		$matcher = new UrlMatcher($this->root, $this->context);
272
		try {
273
			$parameters = $matcher->match($url);
274
		} catch (ResourceNotFoundException $e) {
275
			if (substr($url, -1) !== '/') {
276
				// We allow links to apps/files? for backwards compatibility reasons
277
				// However, since Symfony does not allow empty route names, the route
278
				// we need to match is '/', so we need to append the '/' here.
279
				try {
280
					$parameters = $matcher->match($url . '/');
281
				} catch (ResourceNotFoundException $newException) {
282
					// If we still didn't match a route, we throw the original exception
283
					throw $e;
284
				}
285
			} else {
286
				throw $e;
287
			}
288
		}
289
290
		\OC::$server->getEventLogger()->start('run_route', 'Run route');
291
		if (isset($parameters['caller'])) {
292
			$caller = $parameters['caller'];
293
			unset($parameters['caller']);
294
			$application = $this->getApplicationClass($caller[0]);
295
			\OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters);
296
		} elseif (isset($parameters['action'])) {
297
			$action = $parameters['action'];
298
			if (!is_callable($action)) {
299
				throw new \Exception('not a callable action');
300
			}
301
			unset($parameters['action']);
302
			call_user_func($action, $parameters);
303
		} elseif (isset($parameters['file'])) {
304
			include $parameters['file'];
305
		} else {
306
			throw new \Exception('no action available');
307
		}
308
		\OC::$server->getEventLogger()->end('run_route');
309
	}
310
311
	/**
312
	 * Get the url generator
313
	 *
314
	 * @return \Symfony\Component\Routing\Generator\UrlGenerator
315
	 *
316
	 */
317
	public function getGenerator() {
318
		if (null !== $this->generator) {
319
			return $this->generator;
320
		}
321
322
		return $this->generator = new UrlGenerator($this->root, $this->context);
323
	}
324
325
	/**
326
	 * Generate url based on $name and $parameters
327
	 *
328
	 * @param string $name Name of the route to use.
329
	 * @param array $parameters Parameters for the route
330
	 * @param bool $absolute
331
	 * @return string
332
	 */
333
	public function generate($name,
334
							 $parameters = [],
335
							 $absolute = false) {
336
		$this->loadRoutes();
337
		try {
338
			$referenceType = UrlGenerator::ABSOLUTE_URL;
339
			if ($absolute === false) {
340
				$referenceType = UrlGenerator::ABSOLUTE_PATH;
341
			}
342
			$name = $this->fixLegacyRootName($name);
343
			return $this->getGenerator()->generate($name, $parameters, $referenceType);
344
		} catch (RouteNotFoundException $e) {
345
			$this->logger->logException($e);
346
			return '';
347
		}
348
	}
349
350
	protected function fixLegacyRootName(string $routeName): string {
351
		if ($routeName === 'files.viewcontroller.showFile') {
352
			return 'files.View.showFile';
353
		}
354
		if ($routeName === 'files_sharing.sharecontroller.showShare') {
355
			return 'files_sharing.Share.showShare';
356
		}
357
		if ($routeName === 'files_sharing.sharecontroller.showAuthenticate') {
358
			return 'files_sharing.Share.showAuthenticate';
359
		}
360
		if ($routeName === 'files_sharing.sharecontroller.authenticate') {
361
			return 'files_sharing.Share.authenticate';
362
		}
363
		if ($routeName === 'files_sharing.sharecontroller.downloadShare') {
364
			return 'files_sharing.Share.downloadShare';
365
		}
366
		if ($routeName === 'files_sharing.publicpreview.directLink') {
367
			return 'files_sharing.PublicPreview.directLink';
368
		}
369
		if ($routeName === 'cloud_federation_api.requesthandlercontroller.addShare') {
370
			return 'cloud_federation_api.RequestHandler.addShare';
371
		}
372
		if ($routeName === 'cloud_federation_api.requesthandlercontroller.receiveNotification') {
373
			return 'cloud_federation_api.RequestHandler.receiveNotification';
374
		}
375
		return $routeName;
376
	}
377
378
	/**
379
	 * To isolate the variable scope used inside the $file it is required in it's own method
380
	 *
381
	 * @param string $file the route file location to include
382
	 * @param string $appName
383
	 */
384
	private function requireRouteFile($file, $appName) {
385
		$this->setupRoutes(include_once $file, $appName);
386
	}
387
388
389
	/**
390
	 * If a routes.php file returns an array, try to set up the application and
391
	 * register the routes for the app. The application class will be chosen by
392
	 * camelcasing the appname, e.g.: my_app will be turned into
393
	 * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
394
	 * App will be intialized. This makes it optional to ship an
395
	 * appinfo/application.php by using the built in query resolver
396
	 *
397
	 * @param array $routes the application routes
398
	 * @param string $appName the name of the app.
399
	 */
400
	private function setupRoutes($routes, $appName) {
401
		if (is_array($routes)) {
0 ignored issues
show
introduced by
The condition is_array($routes) is always true.
Loading history...
402
			$application = $this->getApplicationClass($appName);
403
			$application->registerRoutes($this, $routes);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\AppFramework\App::registerRoutes() has been deprecated: 20.0.0 Just return an array from your routes.php ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

403
			/** @scrutinizer ignore-deprecated */ $application->registerRoutes($this, $routes);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

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

Loading history...
404
		}
405
	}
406
407
	private function getApplicationClass(string $appName) {
408
		$appNameSpace = App::buildAppNamespace($appName);
409
410
		$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
411
412
		if (class_exists($applicationClassName)) {
413
			$application = \OC::$server->query($applicationClassName);
414
		} else {
415
			$application = new App($appName);
416
		}
417
418
		return $application;
419
	}
420
}
421