Completed
Push — master ( ad0d0b...75c437 )
by Morris
26:55 queued 13:45
created

Router::useCollection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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 Felix Anand Epp <[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\ILogger;
37
use OCP\Route\IRouter;
38
use OCP\AppFramework\App;
39
use OCP\Util;
40
use Symfony\Component\Routing\Exception\RouteNotFoundException;
41
use Symfony\Component\Routing\Matcher\UrlMatcher;
42
use Symfony\Component\Routing\Generator\UrlGenerator;
43
use Symfony\Component\Routing\RequestContext;
44
use Symfony\Component\Routing\RouteCollection;
45
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
46
47
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...
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 View Code Duplication
			foreach (\OC_APP::getEnabledApps() as $app) {
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');
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
			// Also add the OCS collection
169
			$collection = $this->getCollection('root.ocs');
170
			$collection->addPrefix('/ocsapp');
171
			$this->root->addCollection($collection);
172
		}
173
		if ($this->loaded) {
174
			// include ocs routes, must be loaded last for /ocs prefix
175
			require_once __DIR__ . '/../../../ocs/routes.php';
176
			$collection = $this->getCollection('ocs');
177
			$collection->addPrefix('/ocs');
178
			$this->root->addCollection($collection);
179
		}
180
		\OC::$server->getEventLogger()->end('loadroutes' . $requestedApp);
181
	}
182
183
	/**
184
	 * @return string
185
	 * @deprecated
186
	 */
187
	public function getCacheKey() {
188
		return '';
189
	}
190
191
	/**
192
	 * @param string $name
193
	 * @return \Symfony\Component\Routing\RouteCollection
194
	 */
195
	protected function getCollection($name) {
196
		if (!isset($this->collections[$name])) {
197
			$this->collections[$name] = new RouteCollection();
198
		}
199
		return $this->collections[$name];
200
	}
201
202
	/**
203
	 * Sets the collection to use for adding routes
204
	 *
205
	 * @param string $name Name of the collection to use.
206
	 * @return void
207
	 */
208
	public function useCollection($name) {
209
		$this->collection = $this->getCollection($name);
210
		$this->collectionName = $name;
211
	}
212
213
	/**
214
	 * returns the current collection name in use for adding routes
215
	 *
216
	 * @return string the collection name
217
	 */
218
	public function getCurrentCollection() {
219
		return $this->collectionName;
220
	}
221
222
223
	/**
224
	 * Create a \OC\Route\Route.
225
	 *
226
	 * @param string $name Name of the route to create.
227
	 * @param string $pattern The pattern to match
228
	 * @param array $defaults An array of default parameter values
229
	 * @param array $requirements An array of requirements for parameters (regexes)
230
	 * @return \OC\Route\Route
231
	 */
232
	public function create($name,
233
						   $pattern,
234
						   array $defaults = [],
235
						   array $requirements = []) {
236
		$route = new Route($pattern, $defaults, $requirements);
237
		$this->collection->add($name, $route);
238
		return $route;
239
	}
240
241
	/**
242
	 * Find the route matching $url
243
	 *
244
	 * @param string $url The url to find
245
	 * @throws \Exception
246
	 * @return void
247
	 */
248
	public function match($url) {
249
		if (substr($url, 0, 6) === '/apps/') {
250
			// empty string / 'apps' / $app / rest of the route
251
			list(, , $app,) = explode('/', $url, 4);
252
253
			$app = \OC_App::cleanAppId($app);
254
			\OC::$REQUESTEDAPP = $app;
255
			$this->loadRoutes($app);
256
		} else if (substr($url, 0, 13) === '/ocsapp/apps/') {
257
			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
258
			list(, , , $app,) = explode('/', $url, 5);
259
260
			$app = \OC_App::cleanAppId($app);
261
			\OC::$REQUESTEDAPP = $app;
262
			$this->loadRoutes($app);
263
		} else if (substr($url, 0, 6) === '/core/' or substr($url, 0, 10) === '/settings/') {
264
			\OC::$REQUESTEDAPP = $url;
265
			if (!\OC::$server->getConfig()->getSystemValue('maintenance', false) && !Util::needUpgrade()) {
266
				\OC_App::loadApps();
267
			}
268
			$this->loadRoutes('core');
269
		} else {
270
			$this->loadRoutes();
271
		}
272
273
		$matcher = new UrlMatcher($this->root, $this->context);
274
		try {
275
			$parameters = $matcher->match($url);
276
		} catch (ResourceNotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\Routin...sourceNotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
277
			if (substr($url, -1) !== '/') {
278
				// We allow links to apps/files? for backwards compatibility reasons
279
				// However, since Symfony does not allow empty route names, the route
280
				// we need to match is '/', so we need to append the '/' here.
281
				try {
282
					$parameters = $matcher->match($url . '/');
283
				} catch (ResourceNotFoundException $newException) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\Routin...sourceNotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
284
					// If we still didn't match a route, we throw the original exception
285
					throw $e;
286
				}
287
			} else {
288
				throw $e;
289
			}
290
		}
291
292
		\OC::$server->getEventLogger()->start('run_route', 'Run route');
293
		if (isset($parameters['action'])) {
294
			$action = $parameters['action'];
295
			if (!is_callable($action)) {
296
				throw new \Exception('not a callable action');
297
			}
298
			unset($parameters['action']);
299
			call_user_func($action, $parameters);
300
		} elseif (isset($parameters['file'])) {
301
			include $parameters['file'];
302
		} else {
303
			throw new \Exception('no action available');
304
		}
305
		\OC::$server->getEventLogger()->end('run_route');
306
	}
307
308
	/**
309
	 * Get the url generator
310
	 *
311
	 * @return \Symfony\Component\Routing\Generator\UrlGenerator
312
	 *
313
	 */
314
	public function getGenerator() {
315
		if (null !== $this->generator) {
316
			return $this->generator;
317
		}
318
319
		return $this->generator = new UrlGenerator($this->root, $this->context);
320
	}
321
322
	/**
323
	 * Generate url based on $name and $parameters
324
	 *
325
	 * @param string $name Name of the route to use.
326
	 * @param array $parameters Parameters for the route
327
	 * @param bool $absolute
328
	 * @return string
329
	 */
330
	public function generate($name,
331
							 $parameters = [],
332
							 $absolute = false) {
333
		$this->loadRoutes();
334
		try {
335
			$referenceType = UrlGenerator::ABSOLUTE_URL;
336
			if ($absolute === false) {
337
				$referenceType = UrlGenerator::ABSOLUTE_PATH;
338
			}
339
			return $this->getGenerator()->generate($name, $parameters, $referenceType);
340
		} catch (RouteNotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\Routin...\RouteNotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
341
			$this->logger->logException($e);
342
			return '';
343
		}
344
	}
345
346
	/**
347
	 * To isolate the variable scope used inside the $file it is required in it's own method
348
	 *
349
	 * @param string $file the route file location to include
350
	 * @param string $appName
351
	 */
352
	private function requireRouteFile($file, $appName) {
353
		$this->setupRoutes(include_once $file, $appName);
354
	}
355
356
357
	/**
358
	 * If a routes.php file returns an array, try to set up the application and
359
	 * register the routes for the app. The application class will be chosen by
360
	 * camelcasing the appname, e.g.: my_app will be turned into
361
	 * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
362
	 * App will be intialized. This makes it optional to ship an
363
	 * appinfo/application.php by using the built in query resolver
364
	 *
365
	 * @param array $routes the application routes
366
	 * @param string $appName the name of the app.
367
	 */
368
	private function setupRoutes($routes, $appName) {
369
		if (is_array($routes)) {
370
			$appNameSpace = App::buildAppNamespace($appName);
371
372
			$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
373
374
			if (class_exists($applicationClassName)) {
375
				$application = new $applicationClassName();
376
			} else {
377
				$application = new App($appName);
378
			}
379
380
			$application->registerRoutes($this, $routes);
381
		}
382
	}
383
}
384