Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core\router;
18
 
19
use core\exception\coding_exception;
20
use core\router\schema\parameter;
21
use core\router\schema\response\response;
22
use core\router\schema\request_body;
23
use Attribute;
24
 
25
/**
26
 * Routing attribute.
27
 *
28
 * @package    core
29
 * @copyright  2023 Andrew Lyons <andrew@nicols.co.uk>
30
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 */
32
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
33
class route {
34
    /** @var string[] The list of HTTP Methods */
35
    protected null|array $method = null;
36
 
37
    /**
38
     * The parent route, if relevant.
39
     *
40
     * A method-level route may have a class-level route as a parent. The two are combined to provide
41
     * a fully-qualified path.
42
     *
43
     * @var route|null
44
     */
45
    protected readonly ?route $parentroute;
46
 
47
    /**
48
     * Constructor for a new Moodle route.
49
     *
50
     * @param string $title A title to briefly describe the route (not translated)
51
     * @param string $description A verbose explanation of the operation behavior (not translated)
52
     * @param string $summary A short summary of what the operation does (not translated)
53
     * @param null|string[] $security A list of security mechanisms
54
     * @param null|string $path The path to match
55
     * @param null|array|string $method The method, or methods, supported
56
     * @param parameter[] $pathtypes Validators for the path arguments
57
     * @param parameter[] $queryparams Validators for the path arguments
58
     * @param parameter[] $headerparams Validators for the path arguments
59
     * @param request_body|null $requestbody Validators for the path arguments
60
     * @param response[] $responses A list of possible response types
61
     * @param bool $deprecated Whether this endpoint is deprecated
62
     * @param string[] $tags A list of tags
63
     * @param bool $cookies Whether this request requires cookies
64
     * @param bool $abortafterconfig Whether to abort after configuration
65
     * @param mixed[] ...$extra Any additional arguments not yet supported in this version of Moodle
66
     * @throws coding_exception
67
     */
68
    public function __construct(
69
        /** @var string A title to briefly describe the route (not translated) */
70
        public readonly string $title = '',
71
 
72
        /** @var string A verbose explanation of the operation behavior (not translated) */
73
        public readonly string $description = '',
74
 
75
        /** @var string A short summary of what the operation does (not translated) */
76
        public readonly string $summary = '',
77
 
78
        /** @var array<string> A list of security mechanisms */
79
        public readonly ?array $security = null,
80
 
81
        /**
82
         * The path to the route.
83
         *
84
         * This is relative to the parent route, if one exists.
85
         * A route must be set on one, or both, of the class and method level routes.
86
         *
87
         * @var string|null
88
         */
89
        public ?string $path = null,
90
 
91
        null|array|string $method = null,
92
 
93
        /** @var parameter[] A list of param types for path arguments */
94
        protected readonly array $pathtypes = [],
95
 
96
        /** @var parameter[] A list of query parameters with matching types */
97
        protected readonly array $queryparams = [],
98
 
99
        /** @var parameter[] A list of header parameters */
100
        protected readonly array $headerparams = [],
101
 
102
        /** @var null|request_body A list of parameters found in the body */
103
        public readonly ?request_body $requestbody = null,
104
 
105
        /** @var response[] A list of possible response types */
106
        protected readonly array $responses = [],
107
 
108
        /** @var bool Whether this endpoint is deprecated */
109
        public readonly bool $deprecated = false,
110
 
111
        /** @var string[] A list of tags */
112
        public readonly array $tags = [],
113
 
114
        /** @var bool Whether this request may use cookies */
115
        public readonly bool $cookies = true,
116
 
117
        /** @var bool Whether to abort after configuration */
118
        public readonly bool $abortafterconfig = false,
119
 
120
        /** @var null|array Whether to require login or not */
121
        public readonly ?require_login $requirelogin = null,
122
 
123
        /** @var string[] The list of scopes required to access this page */
124
        public readonly ?array $scopes = null,
125
 
126
        // Note. We do not make use of these extras.
127
        // These allow us to add additional arguments in future versions, whilst allowing plugins to use this version.
128
        ...$extra,
129
    ) {
130
        // Normalise the method.
131
        if (is_string($method)) {
132
            $method = [$method];
133
        }
134
        $this->method = $method;
135
 
136
        // Validate the query parameters.
137
        if (count(array_filter($this->queryparams, fn($pathtype) => !is_a($pathtype, parameter::class)))) {
138
            throw new coding_exception('All query parameters must be an instance of \core\router\parameter.');
139
        }
140
        if (count(array_filter($this->queryparams, fn($pathtype) => $pathtype->get_in() !== 'query'))) {
141
            throw new coding_exception('All query parameters must be in the query.');
142
        }
143
 
144
        // Validate the path parameters.
145
        if (count(array_filter($this->pathtypes, fn($pathtype) => !is_a($pathtype, parameter::class)))) {
146
            throw new coding_exception('All path parameters must be an instance of \core\router\parameter.');
147
        }
148
        if (count(array_filter($this->pathtypes, fn($pathtype) => $pathtype->get_in() !== 'path'))) {
149
            throw new coding_exception('All path properties must be in the path.');
150
        }
151
 
152
        // Validate the header parameters.
153
        if (count(array_filter($this->headerparams, fn($pathtype) => !is_a($pathtype, parameter::class)))) {
154
            throw new coding_exception('All path parameters must be an instance of \core\router\parameter.');
155
        }
156
        if (count(array_filter($this->headerparams, fn($pathtype) => $pathtype->get_in() !== 'header'))) {
157
            throw new coding_exception('All header properties must be in the path.');
158
        }
159
    }
160
 
161
    /**
162
     * Set the parent route, usually a Class-level route.
163
     *
164
     * @param route $parent
165
     * @return self
166
     */
167
    public function set_parent(route $parent): self {
168
        $this->parentroute = $parent;
169
        return $this;
170
    }
171
 
172
    /**
173
     * Get the fully-qualified path for this route relative to root.
174
     *
175
     * This includes the path of any parent route.
176
     *
177
     * @return string
178
     */
179
    public function get_path(): string {
180
        $path = $this->path ?? '';
181
 
182
        if (isset($this->parentroute)) {
183
            $path = $this->parentroute->get_path() . $path;
184
        }
185
        return $path;
186
    }
187
 
188
    /**
189
     * Get the list of HTTP methods associated with this route.
190
     *
191
     * @param null|string[] $default The default methods to use if none are set
192
     * @return null|string[]
193
     */
194
    public function get_methods(?array $default = null): ?array {
195
        $methods = $this->method;
196
 
197
        if (isset($this->parentroute)) {
198
            $parentmethods = $this->parentroute->get_methods();
199
            if ($methods) {
200
                $methods = array_unique(
201
                    array_merge($parentmethods ?? [], $methods),
202
                );
203
            } else {
204
                $methods = $parentmethods;
205
            }
206
        }
207
 
208
        // If there are no methods from either this attribute or any parent, use the default.
209
        $methods = $methods ?? $default;
210
 
211
        if ($methods) {
212
            sort($methods);
213
        }
214
 
215
        return $methods;
216
    }
217
 
218
    /**
219
     * Get the list of path parameters, including any from the parent.
220
     *
221
     * @return array
222
     */
223
    public function get_path_parameters(): array {
224
        $parameters = [];
225
 
226
        if (isset($this->parentroute)) {
227
            $parameters = $this->parentroute->get_path_parameters();
228
        }
229
        foreach ($this->pathtypes as $parameter) {
230
            $parameters[$parameter->get_name()] = $parameter;
231
        }
232
 
233
        return $parameters;
234
    }
235
 
236
    /**
237
     * Get the list of path parameters, including any from the parent.
238
     *
239
     * @return array
240
     */
241
    public function get_header_parameters(): array {
242
        $parameters = [];
243
 
244
        if (isset($this->parentroute)) {
245
            $parameters = $this->parentroute->get_header_parameters();
246
        }
247
        foreach ($this->headerparams as $parameter) {
248
            $parameters[$parameter->get_name()] = $parameter;
249
        }
250
 
251
        return $parameters;
252
    }
253
 
254
    /**
255
     * Get the list of path parameters, including any from the parent.
256
     *
257
     * @return array
258
     */
259
    public function get_query_parameters(): array {
260
        $parameters = [];
261
 
262
        if (isset($this->parentroute)) {
263
            $parameters = $this->parentroute->get_query_parameters();
264
        }
265
        foreach ($this->queryparams as $parameter) {
266
            $parameters[$parameter->get_name()] = $parameter;
267
        }
268
 
269
        return $parameters;
270
    }
271
 
272
    /**
273
     * Get the request body for this route.
274
     *
275
     * @return request_body|null
276
     */
277
    public function get_request_body(): ?request_body {
278
        return $this->requestbody;
279
    }
280
 
281
    /**
282
     * Whether this route expects a request body.
283
     *
284
     * @return bool
285
     */
286
    public function has_request_body(): bool {
287
        return $this->requestbody !== null;
288
    }
289
 
290
    /**
291
     * Get all responses.
292
     *
293
     * @return response[]
294
     */
295
    public function get_responses(): array {
296
        return $this->responses;
297
    }
298
 
299
    /**
300
     * Get the response with the specified response code.
301
     *
302
     * @param int $statuscode
303
     * @return response|null
304
     */
305
    public function get_response_with_status_code(int $statuscode): ?response {
306
        foreach ($this->get_responses() as $response) {
307
            if ($response->get_status_code() === $statuscode) {
308
                return $response;
309
            }
310
        }
311
 
312
        return null;
313
    }
314
 
315
    /**
316
     * Whether this route expects any validatable parameters.
317
     * That is, any parameter in the path, query params, or the request body.
318
     *
319
     * @return bool
320
     */
321
    public function has_any_validatable_parameter(): bool {
322
        return count($this->get_path_parameters()) || count($this->get_query_parameters()) || $this->has_request_body();
323
    }
324
}