mirror of
https://github.com/nextcloud/server.git
synced 2024-11-14 20:36:50 +00:00
b15fdfd40e
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
280 lines
7.5 KiB
PHP
280 lines
7.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
/**
|
|
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
namespace OC\AppFramework\Routing;
|
|
|
|
use OC\AppFramework\DependencyInjection\DIContainer;
|
|
use OC\Route\Router;
|
|
|
|
/**
|
|
* Class RouteConfig
|
|
* @package OC\AppFramework\routing
|
|
*/
|
|
class RouteConfig {
|
|
/** @var DIContainer */
|
|
private $container;
|
|
|
|
/** @var Router */
|
|
private $router;
|
|
|
|
/** @var array */
|
|
private $routes;
|
|
|
|
/** @var string */
|
|
private $appName;
|
|
|
|
/** @var string[] */
|
|
private $controllerNameCache = [];
|
|
|
|
protected $rootUrlApps = [
|
|
'cloud_federation_api',
|
|
'core',
|
|
'files_sharing',
|
|
'files',
|
|
'profile',
|
|
'settings',
|
|
'spreed',
|
|
];
|
|
|
|
/**
|
|
* @param \OC\AppFramework\DependencyInjection\DIContainer $container
|
|
* @param \OC\Route\Router $router
|
|
* @param array $routes
|
|
* @internal param $appName
|
|
*/
|
|
public function __construct(DIContainer $container, Router $router, $routes) {
|
|
$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
|
|
*/
|
|
public function register() {
|
|
// parse simple
|
|
$this->processIndexRoutes($this->routes);
|
|
|
|
// 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.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates one route base on the give configuration
|
|
* @param array $routes
|
|
* @throws \UnexpectedValueException
|
|
*/
|
|
private function processIndexRoutes(array $routes): void {
|
|
$simpleRoutes = $routes['routes'] ?? [];
|
|
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: use the format foo#bar to reference FooController::bar');
|
|
}
|
|
[$controller, $action] = $split;
|
|
|
|
$controllerName = $this->buildControllerName($controller);
|
|
$actionName = $this->buildActionName($action);
|
|
|
|
/*
|
|
* The route name has to be lowercase, for symfony to match it correctly.
|
|
* This is required because smyfony allows mixed casing for controller names in the routes.
|
|
* To avoid breaking all the existing route names, registering and matching will only use the lowercase names.
|
|
* This is also safe on the PHP side because class and method names collide regardless of the casing.
|
|
*/
|
|
$routeName = strtolower($routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix);
|
|
|
|
$router = $this->router->create($routeName, $url)
|
|
->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)) {
|
|
$router->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'] = [$this->appName, $controllerName, $actionName];
|
|
$router->defaults($defaults);
|
|
}
|
|
|
|
/**
|
|
* For a given name and url restful OCS routes are created:
|
|
* - 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
|
|
* - create
|
|
* - update
|
|
* - destroy
|
|
*
|
|
* @param array $routes
|
|
*/
|
|
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 {
|
|
// 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'],
|
|
];
|
|
|
|
foreach ($resources as $resource => $config) {
|
|
$root = $this->buildRootPrefix($config, $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}';
|
|
}
|
|
if (isset($action['url-postfix'])) {
|
|
$url .= '/' . $action['url-postfix'];
|
|
}
|
|
|
|
$controller = $resource;
|
|
|
|
$controllerName = $this->buildControllerName($controller);
|
|
$actionName = $this->buildActionName($method);
|
|
|
|
$routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . $method;
|
|
|
|
$route = $this->router->create($routeName, $url)
|
|
->method($verb);
|
|
|
|
$route->defaults(['caller' => [$this->appName, $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;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|