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\url;
20
use GuzzleHttp\Psr7\Uri;
21
use Psr\Http\Message\ResponseInterface;
22
use Psr\Http\Message\ServerRequestInterface;
23
use Slim\Routing\RouteContext;
24
 
25
/**
26
 * Routing Helper Utilities.
27
 *
28
 * This class includes a variety of helpers for working with routes, including:
29
 * - redirectors
30
 * - callable to route name converters
31
 * - callable to path converters
32
 * - helpers to fetch the \core\router\route instance
33
 *
34
 * @package    core
35
 * @copyright  Andrew Lyons <andrew@nicols.co.uk>
36
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 */
38
class util {
39
    /**
40
     * Redirect to the specified URL, carrying all parameters across too.
41
     *
42
     * @param string|url $path
43
     * @param array $excludeparams Any parameters to exclude from the query params
44
     * @codeCoverageIgnore
45
     */
46
    public static function redirect_with_params(
47
        string|url $path,
48
        array $excludeparams = [],
49
    ): never {
50
        $params = $_GET;
51
        $url = new url(
52
            $path,
53
            $params,
54
        );
55
        $url->remove_params(...$excludeparams);
56
 
57
        redirect($url);
58
    }
59
 
60
    /**
61
     * Redirect to the requested callable.
62
     *
63
     * @param ServerRequestInterface $request
64
     * @param ResponseInterface $response
65
     * @param array|callable|string $callable
66
     * @param null|array $pathparams
67
     * @param null|array $queryparams
68
     * @param null|array $excludeparams A list of any parameters to remove the URI during the redirect
69
     * @return ResponseInterface
70
     */
71
    public static function redirect_to_callable(
72
        ServerRequestInterface $request,
73
        ResponseInterface $response,
74
        array|callable|string $callable,
75
        ?array $pathparams = null,
76
        ?array $queryparams = null,
77
        ?array $excludeparams = null,
78
    ): ResponseInterface {
79
        // Provide defaults for the path and query params if not specified.
80
        if ($pathparams === null) {
81
            $pathparams = $request->getQueryParams();
82
        }
83
        if ($queryparams === null) {
84
            $queryparams = $request->getQueryParams();
85
        }
86
 
87
        // Generate a URI from the callable and the parameters.
88
        $url = self::get_path_for_callable(
89
            $callable,
90
            $pathparams ?? [],
91
            $queryparams ?? [],
92
        );
93
 
94
        // Remove any params.
95
        $url->remove_params($excludeparams);
96
 
97
        return self::redirect($response, $url);
98
    }
99
 
100
    /**
101
     * Generate a Page Not Found result.
102
     *
103
     * @param ServerRequestInterface $request
104
     * @param ResponseInterface $response
105
     * @return ResponseInterface
106
     * @throws \Slim\Exception\HttpNotFoundException
107
     */
108
    public static function throw_page_not_found(
109
        ServerRequestInterface $request,
110
        ResponseInterface $response,
111
    ): ResponseInterface {
112
        throw new \Slim\Exception\HttpNotFoundException($request);
113
    }
114
 
115
    /**
116
     * Redirect to a URL.
117
     *
118
     * @param ResponseInterface $response
119
     * @param string|url $url
120
     * @return ResponseInterface
121
     */
122
    public static function redirect(
123
        ResponseInterface $response,
124
        string|url $url,
125
    ): ResponseInterface {
126
        return $response
127
            ->withStatus(302)
128
            ->withHeader('Location', (string) $url);
129
    }
130
 
131
 
132
    /**
133
     * Get the route name for the specified callable.
134
     *
135
     * @param callable|array|string $callable
136
     * @return string
137
     * @throws \coding_exception If the callable could not be resolved into an Array format
138
     */
139
    public static function get_route_name_for_callable(
140
        callable|array|string $callable,
141
    ): string {
142
        $resolver = \core\di::get(\Invoker\CallableResolver::class);
143
        $callable = $resolver->resolve($callable);
144
 
145
        if (!is_array($callable)) {
146
            throw new \coding_exception('Resolved callable must be in array form');
147
        }
148
 
149
        return get_class($callable[0]) . '::' . $callable[1];
150
    }
151
 
152
    /**
153
     * Get the URI path for the specified callable.
154
     *
155
     * @param string|array|callable $callable the Callable to get the URI for
156
     * @param array $params Any parameters to include in the path
157
     * @param array $queryparams Any parameters to include in the query string
158
     * @return url
159
     */
160
    public static function get_path_for_callable(
161
        string|array|callable $callable,
162
        array $params = [],
163
        array $queryparams = [],
164
    ): url {
165
        global $CFG;
166
 
167
        $router = \core\di::get(\core\router::class);
168
        $app = $router->get_app();
169
        $parser = $app->getRouteCollector()->getRouteParser();
170
 
171
        $routename = self::get_route_name_for_callable($callable);
172
 
173
        return new url(
174
            url: $parser->fullUrlFor(
175
                new Uri($CFG->wwwroot),
176
                $routename,
177
                $params,
178
                $queryparams,
179
            ),
180
        );
181
    }
182
 
183
    /**
184
     * Get the route attribute for the specified request.
185
     *
186
     * @param ServerRequestInterface $request
187
     * @return null|route
188
     */
189
    public static function get_route_instance_for_request(ServerRequestInterface $request): ?route {
190
        if ($route = $request->getAttribute(route::class)) {
191
            return $route;
192
        }
193
 
194
        $context = RouteContext::fromRequest($request);
195
        if ($slimroute = $context->getRoute()) {
196
            return self::get_route_instance_for_method($slimroute->getCallable());
197
        }
198
 
199
        // This should not be encountered - the route should always be set.
200
        return null; // @codeCoverageIgnore
201
    }
202
 
203
    /**
204
     * Get the instance of the \core\router\route attribute for the specified callable if one is available.
205
     *
206
     * @param callable|array|string $callable
207
     * @return null|route The route if one was found.
208
     */
209
    public static function get_route_instance_for_method(callable|array|string $callable): ?route {
210
        // Normalise the callable using the resolver.
211
        // This happens in the same way that Slim does so.
212
        $resolver = \core\di::get(\Invoker\CallableResolver::class);
213
        $callable = $resolver->resolve($callable);
214
 
215
        if (!is_array($callable)) {
216
            // The callable could not be resolved into an array.
217
            return null;
218
        }
219
 
220
        // Locate the Class for this callable.
221
        $classinfo = new \ReflectionClass($callable[0]);
222
 
223
        // Locate the method for this callable.
224
        $methodinfo = $classinfo->getMethod($callable[1]);
225
        if (!$methodinfo) {
226
            // The method does not exist. This shouldn't be possible because the resolver will throw an exception.
227
            return null; // @codeCoverageIgnore
228
        }
229
 
230
        return self::attempt_get_route_instance_for_method($classinfo, $methodinfo);
231
    }
232
 
233
    /**
234
     * Attempt to get the route instance for the specified method, handling any errors in the code along the way.
235
     *
236
     * @param \ReflectionClass $classinfo
237
     * @param \ReflectionMethod $methodinfo
238
     * @return null|route
239
     */
240
    private static function attempt_get_route_instance_for_method(
241
        \ReflectionClass $classinfo,
242
        \ReflectionMethod $methodinfo,
243
    ): ?route {
244
        $instantiator = function (array $attributes) {
245
            global $CFG;
246
            try {
247
                return $attributes ? $attributes[0]->newInstance() : null;
248
            // @codeCoverageIgnoreStart
249
            } catch (\Throwable $e) {
250
                // The route attribute could not be instantiated.
251
                // When debugging, this is useful to know.
252
                // When not, log to error_log.
253
                if (!$CFG->debugdisplay) {
254
                    debugging('Could not instantiate route attribute: ' . $e->getMessage());
255
                    return null;
256
                }
257
 
258
                default_exception_handler($e);
259
            }
260
            // @codeCoverageIgnoreEnd
261
        };
262
 
263
        $methodattributes = $methodinfo->getAttributes(route::class);
264
        $methodroute = $instantiator($methodattributes);
265
 
266
        if (!$methodroute) {
267
            // No route found.
268
            return null;
269
        }
270
 
271
        $classattributes = $classinfo->getAttributes(route::class);
272
        if ($classattributes) {
273
            $classinstance = $instantiator($classattributes);
274
            if ($classinstance) {
275
                // The class has a #route attribute.
276
                $methodroute->set_parent($classinstance);
277
            }
278
        }
279
 
280
        return $methodroute;
281
    }
282
 
283
    /**
284
     * Normalise the component for use as part of the path.
285
     *
286
     * If the component is a subsystem, the `core_` prefix will be removed.
287
     * If the component is 'core', it will be kept.
288
     * All other components will use their frankenstyle name.
289
     *
290
     * @param string $component
291
     * @return string
292
     */
293
    public static function normalise_component_path(
294
        string $component,
295
    ): string {
296
        [$type, $subsystem] = \core\component::normalize_component($component);
297
        if ($type === 'core' && $subsystem !== null) {
298
            return str_replace('core_', '', $subsystem);
299
        }
300
 
301
        return $component;
302
    }
303
}