diff options
author | Morris Jobke <hey@morrisjobke.de> | 2020-11-04 22:16:13 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-04 22:16:13 +0100 |
commit | 645e1c55b82d19b138551063673ea63fbf69fe9a (patch) | |
tree | ef5e7db80ef7d9993f94a664a15a9e4f061d021a /lib | |
parent | 45ef5d1c60cab828186539adc33d15da15b71f01 (diff) | |
parent | e25a7137ccfd4eb64de885256d154665c951f6a4 (diff) | |
download | nextcloud-server-645e1c55b82d19b138551063673ea63fbf69fe9a.tar.gz nextcloud-server-645e1c55b82d19b138551063673ea63fbf69fe9a.zip |
Merge pull request #23819 from nextcloud/td/routing/move_things_around
Cleanup routing
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 1 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 1 | ||||
-rw-r--r-- | lib/private/AppFramework/Routing/RouteParser.php | 269 | ||||
-rw-r--r-- | lib/private/Route/Router.php | 11 |
4 files changed, 280 insertions, 2 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index f9479cc6d21..5c94e2f39f3 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -607,6 +607,7 @@ return array( 'OC\\AppFramework\\OCS\\V2Response' => $baseDir . '/lib/private/AppFramework/OCS/V2Response.php', 'OC\\AppFramework\\Routing\\RouteActionHandler' => $baseDir . '/lib/private/AppFramework/Routing/RouteActionHandler.php', 'OC\\AppFramework\\Routing\\RouteConfig' => $baseDir . '/lib/private/AppFramework/Routing/RouteConfig.php', + 'OC\\AppFramework\\Routing\\RouteParser' => $baseDir . '/lib/private/AppFramework/Routing/RouteParser.php', 'OC\\AppFramework\\ScopedPsrLogger' => $baseDir . '/lib/private/AppFramework/ScopedPsrLogger.php', 'OC\\AppFramework\\Services\\AppConfig' => $baseDir . '/lib/private/AppFramework/Services/AppConfig.php', 'OC\\AppFramework\\Services\\InitialState' => $baseDir . '/lib/private/AppFramework/Services/InitialState.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index a0336fc6bd1..48c311bfbbc 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -636,6 +636,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\AppFramework\\OCS\\V2Response' => __DIR__ . '/../../..' . '/lib/private/AppFramework/OCS/V2Response.php', 'OC\\AppFramework\\Routing\\RouteActionHandler' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Routing/RouteActionHandler.php', 'OC\\AppFramework\\Routing\\RouteConfig' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Routing/RouteConfig.php', + 'OC\\AppFramework\\Routing\\RouteParser' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Routing/RouteParser.php', 'OC\\AppFramework\\ScopedPsrLogger' => __DIR__ . '/../../..' . '/lib/private/AppFramework/ScopedPsrLogger.php', 'OC\\AppFramework\\Services\\AppConfig' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Services/AppConfig.php', 'OC\\AppFramework\\Services\\InitialState' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Services/InitialState.php', diff --git a/lib/private/AppFramework/Routing/RouteParser.php b/lib/private/AppFramework/Routing/RouteParser.php new file mode 100644 index 00000000000..d4d66125825 --- /dev/null +++ b/lib/private/AppFramework/Routing/RouteParser.php @@ -0,0 +1,269 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2016, ownCloud, Inc. + * + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Joas Schilling <coding@schilljs.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <robin@icewind.nl> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\AppFramework\Routing; + +use OC\Route\Route; +use Symfony\Component\Routing\RouteCollection; + +class RouteParser { + /** @var string[] */ + private $controllerNameCache = []; + + private const rootUrlApps = [ + 'cloud_federation_api', + 'core', + 'files_sharing', + 'files', + 'settings', + 'spreed', + ]; + + public function parseDefaultRoutes(array $routes, string $appName): RouteCollection { + $collection = $this->processIndexRoutes($routes, $appName); + $collection->addCollection($this->processIndexResources($routes, $appName)); + + return $collection; + } + + public function parseOCSRoutes(array $routes, string $appName): RouteCollection { + $collection = $this->processOCS($routes, $appName); + $collection->addCollection($this->processOCSResources($routes, $appName)); + + return $collection; + } + + private function processOCS(array $routes, string $appName): RouteCollection { + $collection = new RouteCollection(); + $ocsRoutes = $routes['ocs'] ?? []; + foreach ($ocsRoutes as $ocsRoute) { + $result = $this->processRoute($ocsRoute, $appName, 'ocs.'); + + $collection->add($result[0], $result[1]); + } + + return $collection; + } + + /** + * Creates one route base on the give configuration + * @param array $routes + * @throws \UnexpectedValueException + */ + private function processIndexRoutes(array $routes, string $appName): RouteCollection { + $collection = new RouteCollection(); + $simpleRoutes = $routes['routes'] ?? []; + foreach ($simpleRoutes as $simpleRoute) { + $result = $this->processRoute($simpleRoute, $appName); + + $collection->add($result[0], $result[1]); + } + + return $collection; + } + + private function processRoute(array $route, string $appName, string $routeNamePrefix = ''): array { + $name = $route['name']; + $postfix = $route['postfix'] ?? ''; + $root = $this->buildRootPrefix($route, $appName, $routeNamePrefix); + + $url = $root . '/' . ltrim($route['url'], '/'); + $verb = strtoupper($route['verb'] ?? 'GET'); + + $split = explode('#', $name, 2); + if (count($split) !== 2) { + throw new \UnexpectedValueException('Invalid route name'); + } + list($controller, $action) = $split; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($action); + + $routeName = $routeNamePrefix . $appName . '.' . $controller . '.' . $action . $postfix; + + $routeObject = new Route($url); + $routeObject->method($verb); + + // optionally register requirements for route. This is used to + // tell the route parser how url parameters should be matched + if (array_key_exists('requirements', $route)) { + $routeObject->requirements($route['requirements']); + } + + // optionally register defaults for route. This is used to + // tell the route parser how url parameters should be default valued + $defaults = []; + if (array_key_exists('defaults', $route)) { + $defaults = $route['defaults']; + } + + $defaults['caller'] = [$appName, $controllerName, $actionName]; + $routeObject->defaults($defaults); + + return [$routeName, $routeObject]; + } + + /** + * For a given name and url restful OCS routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $routes + */ + private function processOCSResources(array $routes, string $appName): RouteCollection { + return $this->processResources($routes['ocs-resources'] ?? [], $appName, 'ocs.'); + } + + /** + * For a given name and url restful routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $routes + */ + private function processIndexResources(array $routes, string $appName): RouteCollection { + return $this->processResources($routes['resources'] ?? [], $appName); + } + + /** + * For a given name and url restful routes are created: + * - index + * - show + * - create + * - update + * - destroy + * + * @param array $resources + * @param string $routeNamePrefix + */ + private function processResources(array $resources, string $appName, string $routeNamePrefix = ''): RouteCollection { + // declaration of all restful actions + $actions = [ + ['name' => 'index', 'verb' => 'GET', 'on-collection' => true], + ['name' => 'show', 'verb' => 'GET'], + ['name' => 'create', 'verb' => 'POST', 'on-collection' => true], + ['name' => 'update', 'verb' => 'PUT'], + ['name' => 'destroy', 'verb' => 'DELETE'], + ]; + + $collection = new RouteCollection(); + foreach ($resources as $resource => $config) { + $root = $this->buildRootPrefix($config, $appName, $routeNamePrefix); + + // the url parameter used as id to the resource + foreach ($actions as $action) { + $url = $root . '/' . ltrim($config['url'], '/'); + $method = $action['name']; + + $verb = strtoupper($action['verb'] ?? 'GET'); + $collectionAction = $action['on-collection'] ?? false; + if (!$collectionAction) { + $url .= '/{id}'; + } + + $controller = $resource; + + $controllerName = $this->buildControllerName($controller); + $actionName = $this->buildActionName($method); + + $routeName = $routeNamePrefix . $appName . '.' . strtolower($resource) . '.' . $method; + + $route = new Route($url); + $route->method($verb); + + $route->defaults(['caller' => [$appName, $controllerName, $actionName]]); + + $collection->add($routeName, $route); + } + } + + return $collection; + } + + private function buildRootPrefix(array $route, string $appName, string $routeNamePrefix): string { + $defaultRoot = $appName === 'core' ? '' : '/apps/' . $appName; + $root = $route['root'] ?? $defaultRoot; + + if ($routeNamePrefix !== '') { + // In OCS all apps are whitelisted + return $root; + } + + if (!\in_array($appName, self::rootUrlApps, true)) { + // Only allow root URLS for some apps + return $defaultRoot; + } + + return $root; + } + + /** + * Based on a given route name the controller name is generated + * @param string $controller + * @return string + */ + private function buildControllerName(string $controller): string { + if (!isset($this->controllerNameCache[$controller])) { + $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; + } + return $this->controllerNameCache[$controller]; + } + + /** + * Based on the action part of the route name the controller method name is generated + * @param string $action + * @return string + */ + private function buildActionName(string $action): string { + return $this->underScoreToCamelCase($action); + } + + /** + * Underscored strings are converted to camel case strings + * @param string $str + * @return string + */ + private function underScoreToCamelCase(string $str): string { + $pattern = '/_[a-z]?/'; + return preg_replace_callback( + $pattern, + function ($matches) { + return strtoupper(ltrim($matches[0], '_')); + }, + $str); + } +} diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php index 94c637e5e0d..4e1264666e4 100644 --- a/lib/private/Route/Router.php +++ b/lib/private/Route/Router.php @@ -33,6 +33,7 @@ namespace OC\Route; +use OC\AppFramework\Routing\RouteParser; use OCP\AppFramework\App; use OCP\ILogger; use OCP\Route\IRouter; @@ -426,8 +427,14 @@ class Router implements IRouter { */ private function setupRoutes($routes, $appName) { if (is_array($routes)) { - $application = $this->getApplicationClass($appName); - $application->registerRoutes($this, $routes); + $routeParser = new RouteParser(); + + $defaultRoutes = $routeParser->parseDefaultRoutes($routes, $appName); + $ocsRoutes = $routeParser->parseOCSRoutes($routes, $appName); + + $this->root->addCollection($defaultRoutes); + $ocsRoutes->addPrefix('/ocsapp'); + $this->root->addCollection($ocsRoutes); } } |