nextcloud/lib/private/AppFramework/Routing/RouteConfig.php

297 lines
8.0 KiB
PHP
Raw Normal View History

2013-08-17 13:16:48 +04:00
<?php
declare(strict_types=1);
2013-08-17 13:16:48 +04:00
/**
2016-07-21 18:07:57 +03:00
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
2015-03-26 13:44:34 +03:00
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Joas Schilling <coding@schilljs.com>
2015-03-26 13:44:34 +03:00
* @author Morris Jobke <hey@morrisjobke.de>
* @author Patrick Paysant <ppaysant@linagora.com>
2016-07-21 19:13:36 +03:00
* @author Robin Appelman <robin@icewind.nl>
2016-01-12 17:02:16 +03:00
* @author Robin McCorkell <robin@mccorkell.me.uk>
2016-07-21 18:07:57 +03:00
* @author Roeland Jago Douma <roeland@famdouma.nl>
2015-03-26 13:44:34 +03:00
* @author Thomas Müller <thomas.mueller@tmit.eu>
2013-08-17 13:16:48 +04:00
*
2015-03-26 13:44:34 +03:00
* @license AGPL-3.0
2013-08-17 13:16:48 +04:00
*
2015-03-26 13:44:34 +03:00
* 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.
2013-08-17 13:16:48 +04:00
*
2015-03-26 13:44:34 +03:00
* This program is distributed in the hope that it will be useful,
2013-08-17 13:16:48 +04:00
* but WITHOUT ANY WARRANTY; without even the implied warranty of
2015-03-26 13:44:34 +03:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
2013-08-17 13:16:48 +04:00
*
2015-03-26 13:44:34 +03:00
* 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/>
2013-08-17 13:16:48 +04:00
*
*/
namespace OC\AppFramework\Routing;
2013-08-17 13:16:48 +04:00
use OC\AppFramework\DependencyInjection\DIContainer;
use OCP\Route\IRouter;
2013-08-17 13:16:48 +04:00
/**
* Class RouteConfig
* @package OC\AppFramework\routing
*/
class RouteConfig {
/** @var DIContainer */
2013-08-17 13:16:48 +04:00
private $container;
/** @var IRouter */
2013-08-17 13:16:48 +04:00
private $router;
/** @var array */
2013-08-17 13:16:48 +04:00
private $routes;
/** @var string */
2013-08-17 13:16:48 +04:00
private $appName;
/** @var string[] */
private $controllerNameCache = [];
protected $rootUrlApps = [
'cloud_federation_api',
'core',
'files_sharing',
'files',
'settings',
'spreed',
];
2013-08-17 13:16:48 +04:00
/**
* @param \OC\AppFramework\DependencyInjection\DIContainer $container
* @param \OCP\Route\IRouter $router
* @param array $routes
2013-08-17 13:16:48 +04:00
* @internal param $appName
*/
public function __construct(DIContainer $container, IRouter $router, $routes) {
2013-08-17 13:16:48 +04:00
$this->routes = $routes;
$this->container = $container;
$this->router = $router;
$this->appName = $container['AppName'];
}
/**
* The routes and resource will be registered to the \OCP\Route\IRouter
2013-08-17 13:16:48 +04:00
*/
public function register() {
// parse simple
$this->processIndexRoutes($this->routes);
2013-08-17 13:16:48 +04:00
// parse resources
$this->processIndexResources($this->routes);
/*
* OCS routes go into a different collection
*/
$oldCollection = $this->router->getCurrentCollection();
$this->router->useCollection($oldCollection . '.ocs');
// parse ocs simple routes
$this->processOCS($this->routes);
// parse ocs simple routes
$this->processOCSResources($this->routes);
$this->router->useCollection($oldCollection);
}
private function processOCS(array $routes): void {
$ocsRoutes = $routes['ocs'] ?? [];
foreach ($ocsRoutes as $ocsRoute) {
$this->processRoute($ocsRoute, 'ocs.');
}
2013-08-17 13:16:48 +04:00
}
/**
* Creates one route base on the give configuration
* @param array $routes
2013-08-17 13:16:48 +04:00
* @throws \UnexpectedValueException
*/
private function processIndexRoutes(array $routes): void {
$simpleRoutes = $routes['routes'] ?? [];
2013-08-17 13:16:48 +04:00
foreach ($simpleRoutes as $simpleRoute) {
$this->processRoute($simpleRoute);
}
}
protected function processRoute(array $route, string $routeNamePrefix = ''): void {
$name = $route['name'];
$postfix = $route['postfix'] ?? '';
$root = $this->buildRootPrefix($route, $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 . $this->appName . '.' . $controller . '.' . $action . $postfix;
// register the route
$handler = new RouteActionHandler($this->container, $controllerName, $actionName);
$router = $this->router->create($routeName, $url)
->method($verb)
->action($handler);
// 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)) {
$router->requirements($route['requirements']);
}
// optionally register defaults for route. This is used to
// tell the route parser how url parameters should be default valued
if (array_key_exists('defaults', $route)) {
$router->defaults($route['defaults']);
2013-08-17 13:16:48 +04:00
}
}
/**
* For a given name and url restful OCS routes are created:
2013-08-17 13:16:48 +04:00
* - index
* - show
* - create
* - update
* - destroy
*
* @param array $routes
*/
private function processOCSResources(array $routes): void {
$this->processResources($routes['ocs-resources'] ?? [], 'ocs.');
}
/**
* For a given name and url restful routes are created:
* - index
* - show
2013-08-17 13:16:48 +04:00
* - create
* - update
* - destroy
*
* @param array $routes
2013-08-17 13:16:48 +04:00
*/
private function processIndexResources(array $routes): void {
$this->processResources($routes['resources'] ?? []);
}
/**
* For a given name and url restful routes are created:
* - index
* - show
* - create
* - update
* - destroy
*
* @param array $resources
* @param string $routeNamePrefix
*/
protected function processResources(array $resources, string $routeNamePrefix = ''): void {
2013-08-17 13:16:48 +04:00
// 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'],
];
2013-08-17 13:16:48 +04:00
foreach ($resources as $resource => $config) {
$root = $this->buildRootPrefix($config, $routeNamePrefix);
2013-08-17 13:16:48 +04:00
// the url parameter used as id to the resource
foreach ($actions as $action) {
$url = $root . '/' . ltrim($config['url'], '/');
2013-08-17 13:16:48 +04:00
$method = $action['name'];
$verb = strtoupper($action['verb'] ?? 'GET');
$collectionAction = $action['on-collection'] ?? false;
2013-08-17 13:16:48 +04:00
if (!$collectionAction) {
$url .= '/{id}';
2013-08-17 13:16:48 +04:00
}
if (isset($action['url-postfix'])) {
$url .= '/' . $action['url-postfix'];
2013-08-17 13:16:48 +04:00
}
$controller = $resource;
$controllerName = $this->buildControllerName($controller);
$actionName = $this->buildActionName($method);
$routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . strtolower($method);
2013-08-17 13:16:48 +04:00
$this->router->create($routeName, $url)->method($verb)->action(
2013-08-17 13:16:48 +04:00
new RouteActionHandler($this->container, $controllerName, $actionName)
);
}
}
}
private function buildRootPrefix(array $route, string $routeNamePrefix): string {
$defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName;
$root = $route['root'] ?? $defaultRoot;
if ($routeNamePrefix !== '') {
// In OCS all apps are whitelisted
return $root;
}
if (!\in_array($this->appName, $this->rootUrlApps, true)) {
// Only allow root URLS for some apps
return $defaultRoot;
}
return $root;
}
2013-08-17 13:16:48 +04:00
/**
* Based on a given route name the controller name is generated
* @param string $controller
2013-08-17 13:16:48 +04:00
* @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];
2013-08-17 13:16:48 +04:00
}
/**
* Based on the action part of the route name the controller method name is generated
* @param string $action
2013-08-17 13:16:48 +04:00
* @return string
*/
private function buildActionName(string $action): string {
2013-08-17 13:16:48 +04:00
return $this->underScoreToCamelCase($action);
}
/**
* Underscored strings are converted to camel case strings
* @param string $str
2013-08-17 13:16:48 +04:00
* @return string
*/
private function underScoreToCamelCase(string $str): string {
$pattern = '/_[a-z]?/';
2013-08-17 13:16:48 +04:00
return preg_replace_callback(
$pattern,
function ($matches) {
return strtoupper(ltrim($matches[0], '_'));
2013-08-17 13:16:48 +04:00
},
$str);
}
}