mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 16:32:18 +02:00
This is the beginning of the end for non-routed pages in Moodle and the start of SEO-friendly page URLs. A 'shim' is provided for backwards compatibility.
325 lines
11 KiB
PHP
325 lines
11 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
namespace core\router;
|
|
|
|
use core\exception\coding_exception;
|
|
use core\router\schema\parameter;
|
|
use core\router\schema\response\response;
|
|
use core\router\schema\request_body;
|
|
use Attribute;
|
|
|
|
/**
|
|
* Routing attribute.
|
|
*
|
|
* @package core
|
|
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
|
class route {
|
|
/** @var string[] The list of HTTP Methods */
|
|
protected null|array $method = null;
|
|
|
|
/**
|
|
* The parent route, if relevant.
|
|
*
|
|
* A method-level route may have a class-level route as a parent. The two are combined to provide
|
|
* a fully-qualified path.
|
|
*
|
|
* @var route|null
|
|
*/
|
|
protected readonly ?route $parentroute;
|
|
|
|
/**
|
|
* Constructor for a new Moodle route.
|
|
*
|
|
* @param string $title A title to briefly describe the route (not translated)
|
|
* @param string $description A verbose explanation of the operation behavior (not translated)
|
|
* @param string $summary A short summary of what the operation does (not translated)
|
|
* @param null|string[] $security A list of security mechanisms
|
|
* @param null|string $path The path to match
|
|
* @param null|array|string $method The method, or methods, supported
|
|
* @param parameter[] $pathtypes Validators for the path arguments
|
|
* @param parameter[] $queryparams Validators for the path arguments
|
|
* @param parameter[] $headerparams Validators for the path arguments
|
|
* @param request_body|null $requestbody Validators for the path arguments
|
|
* @param response[] $responses A list of possible response types
|
|
* @param bool $deprecated Whether this endpoint is deprecated
|
|
* @param string[] $tags A list of tags
|
|
* @param bool $cookies Whether this request requires cookies
|
|
* @param bool $abortafterconfig Whether to abort after configuration
|
|
* @param mixed[] ...$extra Any additional arguments not yet supported in this version of Moodle
|
|
* @throws coding_exception
|
|
*/
|
|
public function __construct(
|
|
/** @var string A title to briefly describe the route (not translated) */
|
|
public readonly string $title = '',
|
|
|
|
/** @var string A verbose explanation of the operation behavior (not translated) */
|
|
public readonly string $description = '',
|
|
|
|
/** @var string A short summary of what the operation does (not translated) */
|
|
public readonly string $summary = '',
|
|
|
|
/** @var array<string> A list of security mechanisms */
|
|
public readonly ?array $security = null,
|
|
|
|
/**
|
|
* The path to the route.
|
|
*
|
|
* This is relative to the parent route, if one exists.
|
|
* A route must be set on one, or both, of the class and method level routes.
|
|
*
|
|
* @var string|null
|
|
*/
|
|
public ?string $path = null,
|
|
|
|
null|array|string $method = null,
|
|
|
|
/** @var parameter[] A list of param types for path arguments */
|
|
protected readonly array $pathtypes = [],
|
|
|
|
/** @var parameter[] A list of query parameters with matching types */
|
|
protected readonly array $queryparams = [],
|
|
|
|
/** @var parameter[] A list of header parameters */
|
|
protected readonly array $headerparams = [],
|
|
|
|
/** @var null|request_body A list of parameters found in the body */
|
|
public readonly ?request_body $requestbody = null,
|
|
|
|
/** @var response[] A list of possible response types */
|
|
protected readonly array $responses = [],
|
|
|
|
/** @var bool Whether this endpoint is deprecated */
|
|
public readonly bool $deprecated = false,
|
|
|
|
/** @var string[] A list of tags */
|
|
public readonly array $tags = [],
|
|
|
|
/** @var bool Whether this request may use cookies */
|
|
public readonly bool $cookies = true,
|
|
|
|
/** @var bool Whether to abort after configuration */
|
|
public readonly bool $abortafterconfig = false,
|
|
|
|
/** @var null|array Whether to require login or not */
|
|
public readonly ?require_login $requirelogin = null,
|
|
|
|
/** @var string[] The list of scopes required to access this page */
|
|
public readonly ?array $scopes = null,
|
|
|
|
// Note. We do not make use of these extras.
|
|
// These allow us to add additional arguments in future versions, whilst allowing plugins to use this version.
|
|
...$extra,
|
|
) {
|
|
// Normalise the method.
|
|
if (is_string($method)) {
|
|
$method = [$method];
|
|
}
|
|
$this->method = $method;
|
|
|
|
// Validate the query parameters.
|
|
if (count(array_filter($this->queryparams, fn($pathtype) => !is_a($pathtype, parameter::class)))) {
|
|
throw new coding_exception('All query parameters must be an instance of \core\router\parameter.');
|
|
}
|
|
if (count(array_filter($this->queryparams, fn($pathtype) => $pathtype->get_in() !== 'query'))) {
|
|
throw new coding_exception('All query parameters must be in the query.');
|
|
}
|
|
|
|
// Validate the path parameters.
|
|
if (count(array_filter($this->pathtypes, fn($pathtype) => !is_a($pathtype, parameter::class)))) {
|
|
throw new coding_exception('All path parameters must be an instance of \core\router\parameter.');
|
|
}
|
|
if (count(array_filter($this->pathtypes, fn($pathtype) => $pathtype->get_in() !== 'path'))) {
|
|
throw new coding_exception('All path properties must be in the path.');
|
|
}
|
|
|
|
// Validate the header parameters.
|
|
if (count(array_filter($this->headerparams, fn($pathtype) => !is_a($pathtype, parameter::class)))) {
|
|
throw new coding_exception('All path parameters must be an instance of \core\router\parameter.');
|
|
}
|
|
if (count(array_filter($this->headerparams, fn($pathtype) => $pathtype->get_in() !== 'header'))) {
|
|
throw new coding_exception('All header properties must be in the path.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the parent route, usually a Class-level route.
|
|
*
|
|
* @param route $parent
|
|
* @return self
|
|
*/
|
|
public function set_parent(route $parent): self {
|
|
$this->parentroute = $parent;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the fully-qualified path for this route relative to root.
|
|
*
|
|
* This includes the path of any parent route.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_path(): string {
|
|
$path = $this->path ?? '';
|
|
|
|
if (isset($this->parentroute)) {
|
|
$path = $this->parentroute->get_path() . $path;
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Get the list of HTTP methods associated with this route.
|
|
*
|
|
* @param null|string[] $default The default methods to use if none are set
|
|
* @return null|string[]
|
|
*/
|
|
public function get_methods(?array $default = null): ?array {
|
|
$methods = $this->method;
|
|
|
|
if (isset($this->parentroute)) {
|
|
$parentmethods = $this->parentroute->get_methods();
|
|
if ($methods) {
|
|
$methods = array_unique(
|
|
array_merge($parentmethods ?? [], $methods),
|
|
);
|
|
} else {
|
|
$methods = $parentmethods;
|
|
}
|
|
}
|
|
|
|
// If there are no methods from either this attribute or any parent, use the default.
|
|
$methods = $methods ?? $default;
|
|
|
|
if ($methods) {
|
|
sort($methods);
|
|
}
|
|
|
|
return $methods;
|
|
}
|
|
|
|
/**
|
|
* Get the list of path parameters, including any from the parent.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_path_parameters(): array {
|
|
$parameters = [];
|
|
|
|
if (isset($this->parentroute)) {
|
|
$parameters = $this->parentroute->get_path_parameters();
|
|
}
|
|
foreach ($this->pathtypes as $parameter) {
|
|
$parameters[$parameter->get_name()] = $parameter;
|
|
}
|
|
|
|
return $parameters;
|
|
}
|
|
|
|
/**
|
|
* Get the list of path parameters, including any from the parent.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_header_parameters(): array {
|
|
$parameters = [];
|
|
|
|
if (isset($this->parentroute)) {
|
|
$parameters = $this->parentroute->get_header_parameters();
|
|
}
|
|
foreach ($this->headerparams as $parameter) {
|
|
$parameters[$parameter->get_name()] = $parameter;
|
|
}
|
|
|
|
return $parameters;
|
|
}
|
|
|
|
/**
|
|
* Get the list of path parameters, including any from the parent.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_query_parameters(): array {
|
|
$parameters = [];
|
|
|
|
if (isset($this->parentroute)) {
|
|
$parameters = $this->parentroute->get_query_parameters();
|
|
}
|
|
foreach ($this->queryparams as $parameter) {
|
|
$parameters[$parameter->get_name()] = $parameter;
|
|
}
|
|
|
|
return $parameters;
|
|
}
|
|
|
|
/**
|
|
* Get the request body for this route.
|
|
*
|
|
* @return request_body|null
|
|
*/
|
|
public function get_request_body(): ?request_body {
|
|
return $this->requestbody;
|
|
}
|
|
|
|
/**
|
|
* Whether this route expects a request body.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function has_request_body(): bool {
|
|
return $this->requestbody !== null;
|
|
}
|
|
|
|
/**
|
|
* Get all responses.
|
|
*
|
|
* @return response[]
|
|
*/
|
|
public function get_responses(): array {
|
|
return $this->responses;
|
|
}
|
|
|
|
/**
|
|
* Get the response with the specified response code.
|
|
*
|
|
* @param int $statuscode
|
|
* @return response|null
|
|
*/
|
|
public function get_response_with_status_code(int $statuscode): ?response {
|
|
foreach ($this->get_responses() as $response) {
|
|
if ($response->get_status_code() === $statuscode) {
|
|
return $response;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Whether this route expects any validatable parameters.
|
|
* That is, any parameter in the path, query params, or the request body.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function has_any_validatable_parameter(): bool {
|
|
return count($this->get_path_parameters()) || count($this->get_query_parameters()) || $this->has_request_body();
|
|
}
|
|
}
|