diff options
author | Roeland Jago Douma <rullzer@owncloud.com> | 2016-04-25 21:49:07 +0200 |
---|---|---|
committer | Roeland Jago Douma <rullzer@owncloud.com> | 2016-04-25 21:49:07 +0200 |
commit | bb1e2f9d9d51e83da6ee0da9ce5fb2e415542119 (patch) | |
tree | b5158aa03c9460235f4d30b04cca1d8ff16111ac /lib/private/Route | |
parent | 5e57d3a0b9c42df1413c4d2245faf1bbbe768a63 (diff) | |
download | nextcloud-server-bb1e2f9d9d51e83da6ee0da9ce5fb2e415542119.tar.gz nextcloud-server-bb1e2f9d9d51e83da6ee0da9ce5fb2e415542119.zip |
Move \OC\Route to PSR-4
Diffstat (limited to 'lib/private/Route')
-rw-r--r-- | lib/private/Route/CachingRouter.php | 61 | ||||
-rw-r--r-- | lib/private/Route/Route.php | 157 | ||||
-rw-r--r-- | lib/private/Route/Router.php | 364 |
3 files changed, 582 insertions, 0 deletions
diff --git a/lib/private/Route/CachingRouter.php b/lib/private/Route/CachingRouter.php new file mode 100644 index 00000000000..d6270dcf2c7 --- /dev/null +++ b/lib/private/Route/CachingRouter.php @@ -0,0 +1,61 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Route; + +use OCP\ILogger; + +class CachingRouter extends Router { + /** + * @var \OCP\ICache + */ + protected $cache; + + /** + * @param \OCP\ICache $cache + * @param ILogger $logger + */ + public function __construct($cache, ILogger $logger) { + $this->cache = $cache; + parent::__construct($logger); + } + + /** + * Generate url based on $name and $parameters + * + * @param string $name Name of the route to use. + * @param array $parameters Parameters for the route + * @param bool $absolute + * @return string + */ + public function generate($name, $parameters = array(), $absolute = false) { + asort($parameters); + $key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . $name . sha1(json_encode($parameters)) . intval($absolute); + if ($this->cache->hasKey($key)) { + return $this->cache->get($key); + } else { + $url = parent::generate($name, $parameters, $absolute); + $this->cache->set($key, $url, 3600); + return $url; + } + } +} diff --git a/lib/private/Route/Route.php b/lib/private/Route/Route.php new file mode 100644 index 00000000000..966790a63c0 --- /dev/null +++ b/lib/private/Route/Route.php @@ -0,0 +1,157 @@ +<?php +/** + * @author Bart Visscher <bartv@thisnet.nl> + * @author David Prévot <taffit@debian.org> + * @author Felix Moeller <mail@felixmoeller.de> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Thomas Tanghus <thomas@tanghus.net> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Route; + +use OCP\Route\IRoute; +use Symfony\Component\Routing\Route as SymfonyRoute; + +class Route extends SymfonyRoute implements IRoute { + /** + * Specify the method when this route is to be used + * + * @param string $method HTTP method (uppercase) + * @return \OC\Route\Route + */ + public function method($method) { + $this->setMethods($method); + return $this; + } + + /** + * Specify POST as the method to use with this route + * @return \OC\Route\Route + */ + public function post() { + $this->method('POST'); + return $this; + } + + /** + * Specify GET as the method to use with this route + * @return \OC\Route\Route + */ + public function get() { + $this->method('GET'); + return $this; + } + + /** + * Specify PUT as the method to use with this route + * @return \OC\Route\Route + */ + public function put() { + $this->method('PUT'); + return $this; + } + + /** + * Specify DELETE as the method to use with this route + * @return \OC\Route\Route + */ + public function delete() { + $this->method('DELETE'); + return $this; + } + + /** + * Specify PATCH as the method to use with this route + * @return \OC\Route\Route + */ + public function patch() { + $this->method('PATCH'); + return $this; + } + + /** + * Defaults to use for this route + * + * @param array $defaults The defaults + * @return \OC\Route\Route + */ + public function defaults($defaults) { + $action = $this->getDefault('action'); + $this->setDefaults($defaults); + if (isset($defaults['action'])) { + $action = $defaults['action']; + } + $this->action($action); + return $this; + } + + /** + * Requirements for this route + * + * @param array $requirements The requirements + * @return \OC\Route\Route + */ + public function requirements($requirements) { + $method = $this->getMethods(); + $this->setRequirements($requirements); + if (isset($requirements['_method'])) { + $method = $requirements['_method']; + } + if ($method) { + $this->method($method); + } + return $this; + } + + /** + * The action to execute when this route matches + * + * @param string|callable $class the class or a callable + * @param string $function the function to use with the class + * @return \OC\Route\Route + * + * This function is called with $class set to a callable or + * to the class with $function + */ + public function action($class, $function = null) { + $action = array($class, $function); + if (is_null($function)) { + $action = $class; + } + $this->setDefault('action', $action); + return $this; + } + + /** + * The action to execute when this route matches, includes a file like + * it is called directly + * @param string $file + * @return void + */ + public function actionInclude($file) { + $function = create_function('$param', + 'unset($param["_route"]);' + .'$_GET=array_merge($_GET, $param);' + .'unset($param);' + .'require_once "'.$file.'";'); + $this->action($function); + } +} diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php new file mode 100644 index 00000000000..ef02b5aba0e --- /dev/null +++ b/lib/private/Route/Router.php @@ -0,0 +1,364 @@ +<?php +/** + * @author Bart Visscher <bartv@thisnet.nl> + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Roeland Jago Douma <rullzer@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Route; + +use OCP\ILogger; +use OCP\Route\IRouter; +use OCP\AppFramework\App; +use OCP\Util; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; + +class Router implements IRouter { + /** @var RouteCollection[] */ + protected $collections = []; + /** @var null|RouteCollection */ + protected $collection = null; + /** @var null|string */ + protected $collectionName = null; + /** @var null|RouteCollection */ + protected $root = null; + /** @var null|UrlGenerator */ + protected $generator = null; + /** @var string[] */ + protected $routingFiles; + /** @var bool */ + protected $loaded = false; + /** @var array */ + protected $loadedApps = []; + /** @var ILogger */ + protected $logger; + /** @var RequestContext */ + protected $context; + + /** + * @param ILogger $logger + */ + public function __construct(ILogger $logger) { + $this->logger = $logger; + $baseUrl = \OC::$WEBROOT; + if(!(getenv('front_controller_active') === 'true')) { + $baseUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php'); + } + if (!\OC::$CLI) { + $method = $_SERVER['REQUEST_METHOD']; + } else { + $method = 'GET'; + } + $request = \OC::$server->getRequest(); + $host = $request->getServerHost(); + $schema = $request->getServerProtocol(); + $this->context = new RequestContext($baseUrl, $method, $host, $schema); + // TODO cache + $this->root = $this->getCollection('root'); + } + + /** + * Get the files to load the routes from + * + * @return string[] + */ + public function getRoutingFiles() { + if (!isset($this->routingFiles)) { + $this->routingFiles = []; + foreach (\OC_APP::getEnabledApps() as $app) { + $appPath = \OC_App::getAppPath($app); + if($appPath !== false) { + $file = $appPath . '/appinfo/routes.php'; + if (file_exists($file)) { + $this->routingFiles[$app] = $file; + } + } + } + } + return $this->routingFiles; + } + + /** + * Loads the routes + * + * @param null|string $app + */ + public function loadRoutes($app = null) { + if(is_string($app)) { + $app = \OC_App::cleanAppId($app); + } + + $requestedApp = $app; + if ($this->loaded) { + return; + } + if (is_null($app)) { + $this->loaded = true; + $routingFiles = $this->getRoutingFiles(); + } else { + if (isset($this->loadedApps[$app])) { + return; + } + $file = \OC_App::getAppPath($app) . '/appinfo/routes.php'; + if ($file !== false && file_exists($file)) { + $routingFiles = [$app => $file]; + } else { + $routingFiles = []; + } + } + \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes'); + foreach ($routingFiles as $app => $file) { + if (!isset($this->loadedApps[$app])) { + if (!\OC_App::isAppLoaded($app)) { + // app MUST be loaded before app routes + // try again next time loadRoutes() is called + $this->loaded = false; + continue; + } + $this->loadedApps[$app] = true; + $this->useCollection($app); + $this->requireRouteFile($file, $app); + $collection = $this->getCollection($app); + $collection->addPrefix('/apps/' . $app); + $this->root->addCollection($collection); + } + } + if (!isset($this->loadedApps['core'])) { + $this->loadedApps['core'] = true; + $this->useCollection('root'); + require_once __DIR__ . '/../../../settings/routes.php'; + require_once __DIR__ . '/../../../core/routes.php'; + } + if ($this->loaded) { + // include ocs routes, must be loaded last for /ocs prefix + require_once __DIR__ . '/../../../ocs/routes.php'; + $collection = $this->getCollection('ocs'); + $collection->addPrefix('/ocs'); + $this->root->addCollection($collection); + } + \OC::$server->getEventLogger()->end('loadroutes' . $requestedApp); + } + + /** + * @return string + * @deprecated + */ + public function getCacheKey() { + return ''; + } + + /** + * @param string $name + * @return \Symfony\Component\Routing\RouteCollection + */ + protected function getCollection($name) { + if (!isset($this->collections[$name])) { + $this->collections[$name] = new RouteCollection(); + } + return $this->collections[$name]; + } + + /** + * Sets the collection to use for adding routes + * + * @param string $name Name of the collection to use. + * @return void + */ + public function useCollection($name) { + $this->collection = $this->getCollection($name); + $this->collectionName = $name; + } + + /** + * returns the current collection name in use for adding routes + * + * @return string the collection name + */ + public function getCurrentCollection() { + return $this->collectionName; + } + + + /** + * Create a \OC\Route\Route. + * + * @param string $name Name of the route to create. + * @param string $pattern The pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @return \OC\Route\Route + */ + public function create($name, + $pattern, + array $defaults = [], + array $requirements = []) { + $route = new Route($pattern, $defaults, $requirements); + $this->collection->add($name, $route); + return $route; + } + + /** + * Find the route matching $url + * + * @param string $url The url to find + * @throws \Exception + * @return void + */ + public function match($url) { + if (substr($url, 0, 6) === '/apps/') { + // empty string / 'apps' / $app / rest of the route + list(, , $app,) = explode('/', $url, 4); + + $app = \OC_App::cleanAppId($app); + \OC::$REQUESTEDAPP = $app; + $this->loadRoutes($app); + } else if (substr($url, 0, 6) === '/core/' or substr($url, 0, 10) === '/settings/') { + \OC::$REQUESTEDAPP = $url; + if (!\OC::$server->getConfig()->getSystemValue('maintenance', false) && !Util::needUpgrade()) { + \OC_App::loadApps(); + } + $this->loadRoutes('core'); + } else { + $this->loadRoutes(); + } + + $matcher = new UrlMatcher($this->root, $this->context); + try { + $parameters = $matcher->match($url); + } catch (ResourceNotFoundException $e) { + if (substr($url, -1) !== '/') { + // We allow links to apps/files? for backwards compatibility reasons + // However, since Symfony does not allow empty route names, the route + // we need to match is '/', so we need to append the '/' here. + try { + $parameters = $matcher->match($url . '/'); + } catch (ResourceNotFoundException $newException) { + // If we still didn't match a route, we throw the original exception + throw $e; + } + } else { + throw $e; + } + } + + \OC::$server->getEventLogger()->start('run_route', 'Run route'); + if (isset($parameters['action'])) { + $action = $parameters['action']; + if (!is_callable($action)) { + throw new \Exception('not a callable action'); + } + unset($parameters['action']); + call_user_func($action, $parameters); + } elseif (isset($parameters['file'])) { + include $parameters['file']; + } else { + throw new \Exception('no action available'); + } + \OC::$server->getEventLogger()->end('run_route'); + } + + /** + * Get the url generator + * + * @return \Symfony\Component\Routing\Generator\UrlGenerator + * + */ + public function getGenerator() { + if (null !== $this->generator) { + return $this->generator; + } + + return $this->generator = new UrlGenerator($this->root, $this->context); + } + + /** + * Generate url based on $name and $parameters + * + * @param string $name Name of the route to use. + * @param array $parameters Parameters for the route + * @param bool $absolute + * @return string + */ + public function generate($name, + $parameters = [], + $absolute = false) { + $this->loadRoutes(); + try { + $referenceType = UrlGenerator::ABSOLUTE_URL; + if ($absolute === false) { + $referenceType = UrlGenerator::ABSOLUTE_PATH; + } + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } catch (RouteNotFoundException $e) { + $this->logger->logException($e); + return ''; + } + } + + /** + * To isolate the variable scope used inside the $file it is required in it's own method + * + * @param string $file the route file location to include + * @param string $appName + */ + private function requireRouteFile($file, $appName) { + $this->setupRoutes(include_once $file, $appName); + } + + + /** + * If a routes.php file returns an array, try to set up the application and + * register the routes for the app. The application class will be chosen by + * camelcasing the appname, e.g.: my_app will be turned into + * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default + * App will be intialized. This makes it optional to ship an + * appinfo/application.php by using the built in query resolver + * + * @param array $routes the application routes + * @param string $appName the name of the app. + */ + private function setupRoutes($routes, $appName) { + if (is_array($routes)) { + $appNameSpace = App::buildAppNamespace($appName); + + $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; + + if (class_exists($applicationClassName)) { + $application = new $applicationClassName(); + } else { + $application = new App($appName); + } + + $application->registerRoutes($this, $routes); + } + } +} |