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;
18
 
19
use core\router\middleware\cors_middleware;
20
use core\router\middleware\error_handling_middleware;
21
use core\router\middleware\moodle_api_authentication_middleware;
22
use core\router\middleware\moodle_authentication_middleware;
23
use core\router\middleware\moodle_bootstrap_middleware;
24
use core\router\middleware\moodle_route_attribute_middleware;
25
use core\router\middleware\uri_normalisation_middleware;
26
use core\router\middleware\validation_middleware;
27
use core\router\request_validator_interface;
28
use core\router\response_handler;
29
use core\router\response_validator_interface;
30
use core\router\route_loader_interface;
31
use Psr\Http\Message\ResponseFactoryInterface;
32
use Psr\Http\Message\ResponseInterface;
33
use Psr\Http\Message\ServerRequestInterface;
34
use Slim\App;
35
use Slim\Exception\HttpForbiddenException;
36
use Slim\Exception\HttpNotFoundException;
37
use Slim\Interfaces\RouteGroupInterface;
38
use Slim\Middleware\ErrorMiddleware;
39
 
40
/**
41
 * Moodle Router.
42
 *
43
 * This class represents the Moodle Router, which handles all aspects of Routing in Moodle.
44
 *
45
 * It should not normally be accessed or used outside of its own unit tests, the route_testcase, and the `r.php` handler.
46
 *
47
 * @package    core
48
 * @copyright  Andrew Lyons <andrew@nicols.co.uk>
49
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50
 */
51
class router {
52
    /** @var string The base path to use for all requests */
53
    public readonly string $basepath;
54
 
55
    /** @var App The SlimPHP App */
56
    protected readonly App $app;
57
 
58
    /**
59
     * Create a new Router.
60
     *
61
     * @param response_handler $responsehandler
62
     * @param route_loader_interface $routeloader
63
     * @param request_validator_interface $requestvalidator
64
     * @param response_validator_interface $responsevalidator
65
     * @param null|string $basepath
66
     */
67
    public function __construct(
68
        /** @var response_handler */
69
        protected response_handler $responsehandler,
70
 
71
        /** @var route_loader_interface The router loader to use */
72
        protected readonly route_loader_interface $routeloader,
73
 
74
        /** @var request_validator_interface */
75
        protected request_validator_interface $requestvalidator,
76
 
77
        /** @var response_validator_interface */
78
        protected response_validator_interface $responsevalidator,
79
 
80
        ?string $basepath = null,
81
    ) {
82
        if ($basepath === null) {
83
            $basepath = $this->guess_basepath();
84
        }
85
        $this->basepath = $basepath;
86
    }
87
 
88
    /**
89
     * Guess the basepath for the Router.
90
     *
91
     * @return string
92
     */
93
    protected function guess_basepath(): string {
94
        global $CFG;
95
 
96
        // Moodle is not guaranteed to exist at the domain root.
97
        // Strip out the current script.
98
        $scriptroot = parse_url($CFG->wwwroot, PHP_URL_PATH) ?? '';
99
        $scriptfile = str_replace(
100
            realpath($CFG->dirroot),
101
            '',
102
            realpath($_SERVER['SCRIPT_FILENAME']),
103
        );
104
        // Replace occurrences of backslashes with forward slashes, especially on Windows.
105
        $scriptfile = str_replace('\\', '/', $scriptfile);
106
 
107
        // The server is not configured to rewrite unknown requests to automatically use the router.
108
        $userphp = false;
109
        if ($_SERVER && array_key_exists('REQUEST_URI', $_SERVER)) {
110
            if (str_starts_with($_SERVER['REQUEST_URI'], "{$scriptroot}/r.php")) {
111
                $userphp = true;
112
            }
113
        }
114
 
115
        if ($CFG->routerconfigured !== true || $userphp) {
116
            $scriptroot .= '/r.php';
117
        }
118
 
119
        return $scriptroot;
120
    }
121
 
122
    /**
123
     * Get the configured SlimPHP Application.
124
     *
125
     * @return App
126
     */
127
    public function get_app(): App {
128
        if (!isset($this->app)) {
129
            $this->create_app($this->basepath);
130
        }
131
 
132
        return $this->app;
133
    }
134
 
135
    /**
136
     * Get the Response Factory for the Router.
137
     *
138
     * @return ResponseFactoryInterface
139
     */
140
    public function get_response_factory(): ResponseFactoryInterface {
141
        return $this->get_app()->getResponseFactory();
142
    }
143
 
144
    /**
145
     * Create the configured SlimPHP Application.
146
     *
147
     * @param string $basepath The base path of the Moodle instance
148
     */
149
    protected function create_app(
150
        string $basepath = '',
151
    ): void {
152
        // Create an App using the DI Bridge.
153
        $this->app = router\bridge::create();
154
 
155
        // Add Middleware to the App.
156
        // Note: App Middleware is called before any Group or Route middleware.
157
        $this->add_middleware();
158
        $this->configure_caching();
159
        $this->configure_routes();
160
 
161
        // Configure the basepath for Moodle.
162
        $this->app->setBasePath($basepath);
163
    }
164
 
165
    /**
166
     * Add Middleware to the App.
167
     */
168
    protected function add_middleware(): void {
169
        // Middleware is added like an onion.
170
        // For a Response, the outer-most middleware is executed first, and the inner-most middleware is executed last.
171
        // For a Request, the inner-most middleware is executed first, and the outer-most middleware is executed last.
172
 
173
        // Add the body parsing middleware from Slim.
174
        // See https://www.slimframework.com/docs/v4/middleware/body-parsing.html for further information.
175
        $this->app->addBodyParsingMiddleware();
176
 
177
        // Add Middleware to Bootstrap Moodle from a request.
178
        $this->app->add(di::get(moodle_bootstrap_middleware::class));
179
 
180
        // Add the Moodle route attribute to the request.
181
        // This must be processed after the Routing Middleware has been processed on the request.
182
        $this->app->add(di::get(moodle_route_attribute_middleware::class));
183
 
184
        // Add the Routing Middleware as one of the outer-most middleware.
185
        // This allows the Route to be accessed before it is handled.
186
        // See https://www.slimframework.com/docs/v4/cookbook/retrieving-current-route.html for further information.
187
        $this->app->addRoutingMiddleware();
188
 
189
        // Add request normalisation middleware to standardise the URI.
190
        // This must be done before the Routing Middleware to ensure that the route is matched correctly.
191
        $this->app->add(di::get(uri_normalisation_middleware::class));
192
 
193
        // Add the Error Handling Middleware to the App.
194
        $this->add_error_handler_middleware();
195
    }
196
 
197
    /**
198
     * Add the Error Handling Middleware to the RouteGroup.
199
     */
200
    protected function add_error_handler_middleware(): void {
201
        // Add the Error Handling Middleware and configure it to show Moodle Errors for HTML pages.
202
        $errormiddleware = new ErrorMiddleware(
203
            $this->app->getCallableResolver(),
204
            $this->app->getResponseFactory(),
205
            displayErrorDetails: true,
206
            logErrors: true,
207
            logErrorDetails: true,
208
        );
209
 
210
        // Set a custom error handler for the HttpNotFoundException and HttpForbiddenException.
211
        // We route these to a custom error handler to ensure that the error is displayed with a feedback form.
212
        $errormiddleware->setErrorHandler(
213
            [
214
                HttpNotFoundException::class,
215
                HttpForbiddenException::class,
216
            ],
217
            new router\error_handler($this->app),
218
        );
219
 
220
        $errormiddleware->getDefaultErrorHandler()->registerErrorRenderer('text/html', router\error_renderer::class);
221
 
222
        $this->app->add($errormiddleware);
223
    }
224
 
225
    /**
226
     * Configure the API routes.
227
     */
228
    protected function configure_routes(): void {
229
        $routegroups = $this->routeloader->configure_routes($this->app);
230
        foreach ($routegroups as $name => $collection) {
231
            match ($name) {
232
                route_loader_interface::ROUTE_GROUP_API => $this->configure_api_route($collection),
233
                route_loader_interface::ROUTE_GROUP_PAGE => $this->configure_standard_route($collection),
234
                route_loader_interface::ROUTE_GROUP_SHIM => $this->configure_shim_route($collection),
235
                default => null,
236
            };
237
        }
238
    }
239
 
240
    /**
241
     * Configure the API Route Middleware.
242
     *
243
     * @param RouteGroupInterface $group
244
     */
245
    protected function configure_api_route(RouteGroupInterface $group): void {
246
        $group
247
            ->add(di::get(error_handling_middleware::class))
248
            // Add a Middleware to set the CORS headers for all REST Responses.
249
            ->add(di::get(cors_middleware::class))
250
            ->add(di::get(moodle_api_authentication_middleware::class))
251
            ->add(di::get(validation_middleware::class));
252
    }
253
 
254
    /**
255
     * Configure the Standard page Route Middleware.
256
     *
257
     * @param RouteGroupInterface $group
258
     */
259
    protected function configure_standard_route(RouteGroupInterface $group): void {
260
        $group
261
            ->add(di::get(moodle_authentication_middleware::class))
262
            ->add(di::get(validation_middleware::class));
263
    }
264
 
265
    /**
266
     * Configure the Shim Route Middleware.
267
     *
268
     * @param RouteGroupInterface $group
269
     */
270
    protected function configure_shim_route(RouteGroupInterface $group): void {
271
        $group
272
            // Note: In the future we may wish to add a shim middleware to notify users of updated bookmarks.
273
            ->add(di::get(validation_middleware::class));
274
    }
275
 
276
    /**
277
     * Configure caching for the routes.
278
     */
279
    protected function configure_caching(): void {
280
        global $CFG;
281
 
282
        // Note: Slim uses a file cache and is not compatible with MUC.
283
        $this->app->getRouteCollector()->setCacheFile(
284
            sprintf(
285
                "%s/routes.%s.cache",
286
                $CFG->cachedir,
287
                sha1($this->basepath),
288
            ),
289
        );
290
    }
291
 
292
    /**
293
     * Handle the specified Request.
294
     *
295
     * @param ServerRequestInterface $request
296
     * @return ResponseInterface
297
     */
298
    public function handle_request(
299
        ServerRequestInterface $request,
300
    ): ResponseInterface {
301
        return $this->get_app()->handle($request);
302
    }
303
 
304
    /**
305
     * Serve the current request using global variables.
306
     *
307
     * @codeCoverageIgnore
308
     */
309
    public function serve(): void {
310
        $this->get_app()->run();
311
    }
312
}