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\context\user as context_user;
20
use core\exception\coding_exception;
21
use core\exception\moodle_exception;
22
use Psr\Http\Message\UriInterface;
23
 
24
/**
25
 * Class for creating and manipulating urls.
26
 *
27
 * It can be used in moodle pages where config.php has been included without any further includes.
28
 *
29
 * It is useful for manipulating urls with long lists of params.
30
 * One situation where it will be useful is a page which links to itself to perform various actions
31
 * and / or to process form data. A url object:
32
 * can be created for a page to refer to itself with all the proper get params being passed from page call to
33
 * page call and methods can be used to output a url including all the params, optionally adding and overriding
34
 * params and can also be used to
35
 *     - output the url without any get params
36
 *     - and output the params as hidden fields to be output within a form
37
 *
38
 * @copyright 2007 jamiesensei
39
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40
 * @package core
41
 */
42
class url {
43
    /**
44
     * Scheme, ex.: http, https
45
     * @var string
46
     */
47
    protected $scheme = '';
48
 
49
    /**
50
     * Hostname.
51
     * @var string
52
     */
53
    protected $host = '';
54
 
55
    /**
56
     * Port number, empty means default 80 or 443 in case of http.
57
     * @var int
58
     */
59
    protected $port = '';
60
 
61
    /**
62
     * Username for http auth.
63
     * @var string
64
     */
65
    protected $user = '';
66
 
67
    /**
68
     * Password for http auth.
69
     * @var string
70
     */
71
    protected $pass = '';
72
 
73
    /**
74
     * Script path.
75
     * @var string
76
     */
77
    protected $path = '';
78
 
79
    /**
80
     * Optional slash argument value.
81
     * @var string
82
     */
83
    protected $slashargument = '';
84
 
85
    /**
86
     * Anchor, may be also empty, null means none.
87
     * @var string
88
     */
89
    protected $anchor = null;
90
 
91
    /**
92
     * Url parameters as associative array.
93
     * @var array
94
     */
95
    protected $params = [];
96
 
97
    /**
98
     * Create new instance of url.
99
     *
100
     * @param self|string $url - moodle_url means make a copy of another
101
     *      moodle_url and change parameters, string means full url or shortened
102
     *      form (ex.: '/course/view.php'). It is strongly encouraged to not include
103
     *      query string because it may result in double encoded values. Use the
104
     *      $params instead. For admin URLs, just use /admin/script.php, this
105
     *      class takes care of the $CFG->admin issue.
106
     * @param null|array $params these params override current params or add new
107
     * @param string $anchor The anchor to use as part of the URL if there is one.
108
     * @throws moodle_exception
109
     */
110
    public function __construct(
111
        $url,
112
        ?array $params = null,
113
        $anchor = null,
114
    ) {
115
        global $CFG;
116
 
117
        if ($url instanceof self) {
118
            $this->scheme = $url->scheme;
119
            $this->host = $url->host;
120
            $this->port = $url->port;
121
            $this->user = $url->user;
122
            $this->pass = $url->pass;
123
            $this->path = $url->path;
124
            $this->slashargument = $url->slashargument;
125
            $this->params = $url->params;
126
            $this->anchor = $url->anchor;
127
        } else {
128
            $url = $url ?? '';
129
            // Detect if anchor used.
130
            $apos = strpos($url, '#');
131
            if ($apos !== false) {
132
                $anchor = substr($url, $apos);
133
                $anchor = ltrim($anchor, '#');
134
                $this->set_anchor($anchor);
135
                $url = substr($url, 0, $apos);
136
            }
137
 
138
            // Normalise shortened form of our url ex.: '/course/view.php'.
139
            if (strpos($url, '/') === 0) {
140
                $url = $CFG->wwwroot . $url;
141
            }
142
 
143
            if ($CFG->admin !== 'admin') {
144
                if (strpos($url, "$CFG->wwwroot/admin/") === 0) {
145
                    $url = str_replace("$CFG->wwwroot/admin/", "$CFG->wwwroot/$CFG->admin/", $url);
146
                }
147
            }
148
 
149
            // Parse the $url.
150
            $parts = parse_url($url);
151
            if ($parts === false) {
152
                throw new moodle_exception('invalidurl');
153
            }
154
            if (isset($parts['query'])) {
155
                // Note: the values may not be correctly decoded, url parameters should be always passed as array.
156
                $out = [];
157
                parse_str(str_replace('&amp;', '&', $parts['query']), $out);
158
                $this->params($out);
159
            }
160
            unset($parts['query']);
161
            foreach ($parts as $key => $value) {
162
                $this->$key = $value;
163
            }
164
 
165
            // Detect slashargument value from path - we do not support directory names ending with .php.
166
            $pos = strpos($this->path, '.php/');
167
            if ($pos !== false) {
168
                $this->slashargument = substr($this->path, $pos + 4);
169
                $this->path = substr($this->path, 0, $pos + 4);
170
            }
171
        }
172
 
173
        $this->params($params);
174
        if ($anchor !== null) {
175
            $this->anchor = (string)$anchor;
176
        }
177
    }
178
 
179
    /**
180
     * Add an array of params to the params for this url.
181
     *
182
     * The added params override existing ones if they have the same name.
183
     *
184
     * @param null|array $params Array of parameters to add. Note all values that are not arrays are cast to strings.
185
     * @return array Array of Params for url.
186
     * @throws coding_exception
187
     */
188
    public function params(?array $params = null) {
189
        $params = (array)$params;
190
        $params = $this->clean_url_params($params);
191
        $this->params = array_merge($this->params, $params);
192
        return $this->params;
193
    }
194
 
195
    /**
196
     * Converts given URL parameter values that are not arrays into strings.
197
     *
198
     * This is to maintain the same behaviour as the original params() function.
199
     *
200
     * @param array $params
201
     * @return array
202
     */
203
    private function clean_url_params(array $params): array {
204
        // Convert all values to strings.
205
        // This was the original implementation of the params function,
206
        // which we have kept for backwards compatibility.
207
        array_walk_recursive($params, function (&$value) {
208
            if (is_object($value) && !is_a($value, \Stringable::class)) {
209
                throw new coding_exception('Url parameters values can not be objects, unless __toString() is defined!');
210
            }
211
            $value = (string) $value;
212
        });
213
        return $params;
214
    }
215
 
216
    /**
217
     * Remove all params if no arguments passed.
218
     * Remove selected params if arguments are passed.
219
     *
220
     * Can be called as either remove_params('param1', 'param2')
221
     * or remove_params(array('param1', 'param2')).
222
     *
223
     * @param string[]|string ...$params either an array of param names, or 1..n string params to remove as args.
224
     * @return array url parameters
225
     */
226
    public function remove_params(...$params) {
227
        if (empty($params)) {
228
            return $this->params;
229
        }
230
 
231
        $firstparam = reset($params);
232
        if (is_array($firstparam)) {
233
            $params = $firstparam;
234
        }
235
 
236
        foreach ($params as $param) {
237
            unset($this->params[$param]);
238
        }
239
        return $this->params;
240
    }
241
 
242
    /**
243
     * Remove all url parameters.
244
     *
245
     * @param array $unused Unused param
246
     */
247
    public function remove_all_params($unused = null) {
248
        $this->params = [];
249
        $this->slashargument = '';
250
    }
251
 
252
    /**
253
     * Add a param to the params for this url.
254
     *
255
     * The added param overrides existing one if they have the same name.
256
     *
257
     * @param string $paramname name
258
     * @param string $newvalue Param value. If new value specified current value is overriden or parameter is added
259
     * @return array|string|null parameter value, null if parameter does not exist.
260
     */
261
    public function param($paramname, $newvalue = '') {
262
        if (func_num_args() > 1) {
263
            // Set new value.
264
            $this->params([$paramname => $newvalue]);
265
        }
266
        if (isset($this->params[$paramname])) {
267
            return $this->params[$paramname];
268
        } else {
269
            return null;
270
        }
271
    }
272
 
273
    /**
274
     * Merges parameters.
275
     *
276
     * @param null|array $overrideparams
277
     * @return array merged parameters
278
     */
279
    protected function merge_overrideparams(?array $overrideparams = null) {
280
        $overrideparams = (array) $overrideparams;
281
        $overrideparams = $this->clean_url_params($overrideparams);
282
        return array_merge($this->params, $overrideparams);
283
    }
284
 
285
    /**
286
     * Recursively transforms the given array of values to query string parts.
287
     *
288
     * Example query string parts: a=2, a[0]=2
289
     *
290
     * @param array $data Data to encode into query string parts. Can be a multi level array. All end values must be strings.
291
     * @return array array of query string parts. All parts are rawurlencoded.
292
     */
293
    private function recursively_transform_params_to_query_string_parts(array $data): array {
294
        $stringparams = [];
295
 
296
        // Define a recursive function to encode the array into a set of string params.
297
        // We need to do this recursively, so that multi level array parameters are properly supported.
298
        $parsestringparams = function (array $data) use (&$stringparams, &$parsestringparams) {
299
            foreach ($data as $key => $value) {
300
                // If this is an array, rewrite the $value keys to track the position in the array.
301
                // and pass back to this function recursively until the values are no longer arrays.
302
                // E.g. if $key is 'a' and $value was [0 => true, 1 => false]
303
                // the new array becomes ['a[0]' => true, 'a[1]' => false].
304
                if (is_array($value)) {
305
                    $newvalue = [];
306
                    foreach ($value as $innerkey => $innervalue) {
307
                        $newkey = $key . '[' . $innerkey . ']';
308
                        $newvalue[$newkey] = $innervalue;
309
                    }
310
                    $parsestringparams($newvalue);
311
                } else {
312
                    // Else no more arrays to traverse - build the final query string part.
313
                    // We enforce that all end values are strings for consistency.
314
                    // When params() is used, it will convert all params given to strings.
315
                    // This will catch out anyone setting the params property directly.
316
                    if (!is_string($value)) {
317
                        throw new coding_exception('Unexpected query string value type.
318
All values that are not arrays should be a string.');
319
                    }
320
 
321
                    if (isset($value) && $value !== '') {
322
                        $stringparams[] = rawurlencode($key) . '=' . rawurlencode($value);
323
                    } else {
324
                        $stringparams[] = rawurlencode($key);
325
                    }
326
                }
327
            }
328
        };
329
 
330
        $parsestringparams($data);
331
 
332
        return $stringparams;
333
    }
334
 
335
    /**
336
     * Get the params as as a query string.
337
     *
338
     * This method should not be used outside of this method.
339
     *
340
     * @param bool $escaped Use &amp; as params separator instead of plain &
341
     * @param null|array $overrideparams params to add to the output params, these
342
     *      override existing ones with the same name.
343
     * @return string query string that can be added to a url.
344
     */
345
    public function get_query_string($escaped = true, ?array $overrideparams = null) {
346
        if ($overrideparams !== null) {
347
            $params = $this->merge_overrideparams($overrideparams);
348
        } else {
349
            $params = $this->params;
350
        }
351
 
352
        $stringparams = $this->recursively_transform_params_to_query_string_parts($params);
353
 
354
        if ($escaped) {
355
            return implode('&amp;', $stringparams);
356
        } else {
357
            return implode('&', $stringparams);
358
        }
359
    }
360
 
361
    /**
362
     * Get the url params as an array of key => value pairs.
363
     *
364
     * This helps in handling cases where url params contain arrays.
365
     *
366
     * @return array params array for templates.
367
     */
368
    public function export_params_for_template(): array {
369
        $querystringparts = $this->recursively_transform_params_to_query_string_parts($this->params);
370
 
371
        return array_map(function ($value) {
372
            // First urldecode it, they are encoded by default.
373
            $value = rawurldecode($value);
374
 
375
            // Now separate the parts into name and value, splitting only on the first '=' sign.
376
            // There may be more = signs (e.g. base64, encoded urls, etc...) that we don't want to split on.
377
            $parts = explode('=', $value, 2);
378
 
379
            // Parts must be of length 1 or 2, anything else is an invalid.
380
            if (count($parts) !== 1 && count($parts) !== 2) {
381
                throw new coding_exception('Invalid query string construction, unexpected number of parts');
382
            }
383
 
384
            // There might not always be a '=' e.g. when the value is an empty string.
385
            // in this case, just fallback to an empty string.
386
            $name = $parts[0];
387
            $value = $parts[1] ?? '';
388
 
389
            return ['name' => $name, 'value' => $value];
390
        }, $querystringparts);
391
    }
392
 
393
    /**
394
     * Shortcut for printing of encoded URL.
395
     *
396
     * @return string
397
     */
398
    public function __toString() {
399
        return $this->out(true);
400
    }
401
 
402
    /**
403
     * Output url.
404
     *
405
     * If you use the returned URL in HTML code, you want the escaped ampersands. If you use
406
     * the returned URL in HTTP headers, you want $escaped=false.
407
     *
408
     * @param bool $escaped Use &amp; as params separator instead of plain &
409
     * @param null|array $overrideparams params to add to the output url, these override existing ones with the same name.
410
     * @return string Resulting URL
411
     */
412
    public function out($escaped = true, ?array $overrideparams = null) {
413
 
414
        global $CFG;
415
 
416
        if (!is_bool($escaped)) {
417
            debugging('Escape parameter must be of type boolean, ' . gettype($escaped) . ' given instead.');
418
        }
419
 
420
        $url = $this;
421
 
422
        // Allow url's to be rewritten by a plugin.
423
        if (isset($CFG->urlrewriteclass) && !isset($CFG->upgraderunning)) {
424
            $class = $CFG->urlrewriteclass;
425
            $pluginurl = $class::url_rewrite($url);
426
            if ($pluginurl instanceof url) {
427
                $url = $pluginurl;
428
            }
429
        }
430
 
431
        return $url->raw_out($escaped, $overrideparams);
432
    }
433
 
434
    /**
435
     * Output url without any rewrites
436
     *
437
     * This is identical in signature and use to out() but doesn't call the rewrite handler.
438
     *
439
     * @param bool $escaped Use &amp; as params separator instead of plain &
440
     * @param null|array $overrideparams params to add to the output url, these override existing ones with the same name.
441
     * @return string Resulting URL
442
     */
443
    public function raw_out($escaped = true, ?array $overrideparams = null) {
444
        if (!is_bool($escaped)) {
445
            debugging('Escape parameter must be of type boolean, ' . gettype($escaped) . ' given instead.');
446
        }
447
 
448
        $uri = $this->out_omit_querystring() . $this->slashargument;
449
 
450
        $querystring = $this->get_query_string($escaped, $overrideparams);
451
        if ($querystring !== '') {
452
            $uri .= '?' . $querystring;
453
        }
454
 
455
        $uri .= $this->get_encoded_anchor();
456
 
457
        return $uri;
458
    }
459
 
460
    /**
461
     * Encode the anchor according to RFC 3986.
462
     *
463
     * @return string The encoded anchor
464
     */
465
    public function get_encoded_anchor(): string {
466
        if (is_null($this->anchor)) {
467
            return '';
468
        }
469
 
470
        // RFC 3986 allows the following characters in a fragment without them being encoded:
471
        // pct-encoded: "%" HEXDIG HEXDIG
472
        // unreserved:  ALPHA / DIGIT / "-" / "." / "_" / "~" /
473
        // sub-delims:  "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" / ":" / "@"
474
        // fragment:    "/" / "?"
475
        //
476
        // All other characters should be encoded.
477
        // These should not be encoded in the fragment unless they were already encoded.
478
 
479
        // The following characters are allowed in the fragment without encoding.
480
        // In addition to this list is pct-encoded, but we can't easily handle this with a regular expression.
481
        $allowed = 'a-zA-Z0-9\\-._~!$&\'()*+,;=:@\/?';
482
        $anchor = '#';
483
 
484
        $remainder = $this->anchor;
485
        do {
486
            // Split the string on any %.
487
            $parts = explode('%', $remainder, 2);
488
            $anchorparts = array_shift($parts);
489
 
490
            // The first part can go through our preg_replace_callback to quote any relevant characters.
491
            $anchor .= preg_replace_callback(
492
                '/[^' . $allowed . ']/',
493
                fn ($matches) => rawurlencode($matches[0]),
494
                $anchorparts,
495
            );
496
 
497
            // The second part _might_ be a valid pct-encoded character.
498
            if (count($parts) === 0) {
499
                break;
500
            }
501
 
502
            // If the second part is a valid pct-encoded character, append it to the anchor.
503
            $remainder = array_shift($parts);
504
            if (preg_match('/^[a-fA-F0-9]{2}/', $remainder, $matches)) {
505
                $anchor .= "%{$matches[0]}";
506
                $remainder = substr($remainder, 2);
507
            } else {
508
                // This was not a valid pct-encoded character. Encode the % and continue with the next part.
509
                $anchor .= rawurlencode('%');
510
            }
511
        } while (strlen($remainder) > 0);
512
 
513
        return $anchor;
514
    }
515
 
516
    /**
517
     * Returns url without parameters, everything before '?'.
518
     *
519
     * @param bool $includeanchor if {@see self::anchor} is defined, should it be returned?
520
     * @return string
521
     */
522
    public function out_omit_querystring($includeanchor = false) {
523
 
524
        $uri = $this->scheme ? $this->scheme . ':' . ((strtolower($this->scheme) == 'mailto') ? '' : '//') : '';
525
        $uri .= $this->user ? $this->user . ($this->pass ? ':' . $this->pass : '') . '@' : '';
526
        $uri .= $this->host ? $this->host : '';
527
        $uri .= $this->port ? ':' . $this->port : '';
528
        $uri .= $this->path ? $this->path : '';
529
        if ($includeanchor) {
530
            $uri .= $this->get_encoded_anchor();
531
        }
532
 
533
        return $uri;
534
    }
535
 
536
    /**
537
     * Compares this url with another.
538
     *
539
     * See documentation of constants for an explanation of the comparison flags.
540
     *
541
     * @param self $url The moodle_url object to compare
542
     * @param int $matchtype The type of comparison (URL_MATCH_BASE, URL_MATCH_PARAMS, URL_MATCH_EXACT)
543
     * @return bool
544
     */
545
    public function compare(self $url, $matchtype = URL_MATCH_EXACT) {
546
 
547
        $baseself = $this->out_omit_querystring();
548
        $baseother = $url->out_omit_querystring();
549
 
550
        // Append index.php if there is no specific file.
551
        if (substr($baseself, -1) == '/') {
552
            $baseself .= 'index.php';
553
        }
554
        if (substr($baseother, -1) == '/') {
555
            $baseother .= 'index.php';
556
        }
557
 
558
        // Compare the two base URLs.
559
        if ($baseself != $baseother) {
560
            return false;
561
        }
562
 
563
        if ($matchtype == URL_MATCH_BASE) {
564
            return true;
565
        }
566
 
567
        $urlparams = $url->params();
568
        foreach ($this->params() as $param => $value) {
569
            if ($param == 'sesskey') {
570
                continue;
571
            }
572
            if (!array_key_exists($param, $urlparams) || $urlparams[$param] != $value) {
573
                return false;
574
            }
575
        }
576
 
577
        if ($matchtype == URL_MATCH_PARAMS) {
578
            return true;
579
        }
580
 
581
        foreach ($urlparams as $param => $value) {
582
            if ($param == 'sesskey') {
583
                continue;
584
            }
585
            if (!array_key_exists($param, $this->params()) || $this->param($param) != $value) {
586
                return false;
587
            }
588
        }
589
 
590
        if ($url->anchor !== $this->anchor) {
591
            return false;
592
        }
593
 
594
        return true;
595
    }
596
 
597
    /**
598
     * Sets the anchor for the URI (the bit after the hash)
599
     *
600
     * @param string $anchor null means remove previous
601
     */
602
    public function set_anchor($anchor) {
603
        if (is_null($anchor)) {
604
            // Remove.
605
            $this->anchor = null;
606
        } else {
607
            $this->anchor = $anchor;
608
        }
609
    }
610
 
611
    /**
612
     * Sets the scheme for the URI (the bit before ://)
613
     *
614
     * @param string $scheme
615
     */
616
    public function set_scheme($scheme) {
617
        // See http://www.ietf.org/rfc/rfc3986.txt part 3.1.
618
        if (preg_match('/^[a-zA-Z][a-zA-Z0-9+.-]*$/', $scheme)) {
619
            $this->scheme = $scheme;
620
        } else {
621
            throw new coding_exception('Bad URL scheme.');
622
        }
623
    }
624
 
625
    /**
626
     * Sets the url slashargument value.
627
     *
628
     * @param string $path usually file path
629
     * @param string $parameter name of page parameter if slasharguments not supported
630
     * @param bool $supported usually null, then it depends on $CFG->slasharguments, use true or false for other servers
631
     */
632
    public function set_slashargument($path, $parameter = 'file', $supported = null) {
633
        global $CFG;
634
        if (is_null($supported)) {
635
            $supported = !empty($CFG->slasharguments);
636
        }
637
 
638
        if ($supported) {
639
            $parts = explode('/', $path);
640
            $parts = array_map('rawurlencode', $parts);
641
            $path  = implode('/', $parts);
642
            $this->slashargument = $path;
643
            unset($this->params[$parameter]);
644
        } else {
645
            $this->slashargument = '';
646
            $this->params[$parameter] = $path;
647
        }
648
    }
649
 
650
    // Static factory methods.
651
 
652
    /**
653
     * Create a new url instance from a UriInterface.
654
     *
655
     * @param UriInterface $uri
656
     * @return self
657
     */
658
    public static function from_uri(UriInterface $uri): self {
659
        $url = new self(
660
            url: $uri->getScheme() . '://' . $uri->getAuthority() . $uri->getPath(),
661
            anchor: $uri->getFragment() ?: null,
662
        );
663
 
664
        $params = $uri->getQuery();
665
        foreach (explode('&', $params) as $param) {
666
            $url->param(...explode('=', $param, 2));
667
        }
668
 
669
        return $url;
670
    }
671
 
672
    /**
673
     * Create a new moodle_url instance from routed path.
674
     *
675
     * @param string $path The routed path
676
     * @param null|array $params The path parameters
677
     * @param null|string $anchor The anchor
678
     * @return self
679
     */
680
    public static function routed_path(
681
        string $path,
682
        ?array $params = null,
683
        ?string $anchor = null,
684
    ): self {
685
        global $CFG;
686
 
687
        if ($CFG->routerconfigured !== true) {
688
            $path = '/r.php/' . ltrim($path, '/');
689
        }
690
        $url = new self($path, $params, $anchor);
691
        return $url;
692
    }
693
 
694
    /**
695
     * General moodle file url.
696
     *
697
     * @param string $urlbase the script serving the file
698
     * @param string $path
699
     * @param bool $forcedownload
700
     * @return self
701
     */
702
    public static function make_file_url($urlbase, $path, $forcedownload = false) {
703
        $params = [];
704
        if ($forcedownload) {
705
            $params['forcedownload'] = 1;
706
        }
707
        $url = new self($urlbase, $params);
708
        $url->set_slashargument($path);
709
        return $url;
710
    }
711
 
712
    /**
713
     * Factory method for creation of url pointing to plugin file.
714
     *
715
     * Please note this method can be used only from the plugins to
716
     * create urls of own files, it must not be used outside of plugins!
717
     *
718
     * @param int $contextid
719
     * @param string $component
720
     * @param string $area
721
     * @param ?int $itemid
722
     * @param string $pathname
723
     * @param string $filename
724
     * @param bool $forcedownload
725
     * @param mixed $includetoken Whether to use a user token when displaying this group image.
726
     *                True indicates to generate a token for current user, and integer value indicates to generate a token for the
727
     *                user whose id is the value indicated.
728
     *                If the group picture is included in an e-mail or some other location where the audience is a specific
729
     *                user who will not be logged in when viewing, then we use a token to authenticate the user.
730
     * @return url
731
     */
732
    public static function make_pluginfile_url(
733
        $contextid,
734
        $component,
735
        $area,
736
        $itemid,
737
        $pathname,
738
        $filename,
739
        $forcedownload = false,
740
        $includetoken = false
741
    ) {
742
        global $CFG, $USER;
743
 
744
        $path = [];
745
 
746
        if ($includetoken) {
747
            $urlbase = "$CFG->wwwroot/tokenpluginfile.php";
748
            $userid = $includetoken === true ? $USER->id : $includetoken;
749
            $token = get_user_key('core_files', $userid);
750
            if ($CFG->slasharguments) {
751
                $path[] = $token;
752
            }
753
        } else {
754
            $urlbase = "$CFG->wwwroot/pluginfile.php";
755
        }
756
        $path[] = $contextid;
757
        $path[] = $component;
758
        $path[] = $area;
759
 
760
        if ($itemid !== null) {
761
            $path[] = $itemid;
762
        }
763
 
764
        $path = "/" . implode('/', $path) . "{$pathname}{$filename}";
765
 
766
        $url = self::make_file_url($urlbase, $path, $forcedownload, $includetoken);
767
        if ($includetoken && empty($CFG->slasharguments)) {
768
            $url->param('token', $token);
769
        }
770
        return $url;
771
    }
772
 
773
    /**
774
     * Factory method for creation of url pointing to plugin file.
775
     * This method is the same that make_pluginfile_url but pointing to the webservice pluginfile.php script.
776
     * It should be used only in external functions.
777
     *
778
     * @since  2.8
779
     * @param int $contextid
780
     * @param string $component
781
     * @param string $area
782
     * @param int $itemid
783
     * @param string $pathname
784
     * @param string $filename
785
     * @param bool $forcedownload
786
     * @return url
787
     */
788
    public static function make_webservice_pluginfile_url(
789
        $contextid,
790
        $component,
791
        $area,
792
        $itemid,
793
        $pathname,
794
        $filename,
795
        $forcedownload = false
796
    ) {
797
        global $CFG;
798
        $urlbase = "$CFG->wwwroot/webservice/pluginfile.php";
799
        if ($itemid === null) {
800
            return self::make_file_url($urlbase, "/$contextid/$component/$area" . $pathname . $filename, $forcedownload);
801
        } else {
802
            return self::make_file_url($urlbase, "/$contextid/$component/$area/$itemid" . $pathname . $filename, $forcedownload);
803
        }
804
    }
805
 
806
    /**
807
     * Factory method for creation of url pointing to draft file of current user.
808
     *
809
     * @param int $draftid draft item id
810
     * @param string $pathname
811
     * @param string $filename
812
     * @param bool $forcedownload
813
     * @return url
814
     */
815
    public static function make_draftfile_url($draftid, $pathname, $filename, $forcedownload = false) {
816
        global $CFG, $USER;
817
        $urlbase = "$CFG->wwwroot/draftfile.php";
818
        $context = context_user::instance($USER->id);
819
 
820
        return self::make_file_url($urlbase, "/$context->id/user/draft/$draftid" . $pathname . $filename, $forcedownload);
821
    }
822
 
823
    /**
824
     * Factory method for creating of links to legacy course files.
825
     *
826
     * @param int $courseid
827
     * @param string $filepath
828
     * @param bool $forcedownload
829
     * @return url
830
     */
831
    public static function make_legacyfile_url($courseid, $filepath, $forcedownload = false) {
832
        global $CFG;
833
 
834
        $urlbase = "$CFG->wwwroot/file.php";
835
        return self::make_file_url($urlbase, '/' . $courseid . '/' . $filepath, $forcedownload);
836
    }
837
 
838
    /**
839
     * Checks if URL is relative to $CFG->wwwroot.
840
     *
841
     * @return bool True if URL is relative to $CFG->wwwroot; otherwise, false.
842
     */
843
    public function is_local_url(): bool {
844
        global $CFG;
845
 
846
        $url = $this->out();
847
        // Does URL start with wwwroot? Otherwise, URL isn't relative to wwwroot.
848
        return ( ($url === $CFG->wwwroot) || (strpos($url, $CFG->wwwroot . '/') === 0) );
849
    }
850
 
851
    /**
852
     * Returns URL as relative path from $CFG->wwwroot
853
     *
854
     * Can be used for passing around urls with the wwwroot stripped
855
     *
856
     * @param boolean $escaped Use &amp; as params separator instead of plain &
857
     * @param ?array $overrideparams params to add to the output url, these override existing ones with the same name.
858
     * @return string Resulting URL
859
     * @throws coding_exception if called on a non-local url
860
     */
861
    public function out_as_local_url($escaped = true, ?array $overrideparams = null) {
862
        global $CFG;
863
 
864
        // URL should be relative to wwwroot. If not then throw exception.
865
        if ($this->is_local_url()) {
866
            $url = $this->out($escaped, $overrideparams);
867
            $localurl = substr($url, strlen($CFG->wwwroot));
868
            return !empty($localurl) ? $localurl : '';
869
        } else {
870
            throw new coding_exception('out_as_local_url called on a non-local URL');
871
        }
872
    }
873
 
874
    /**
875
     * Returns the 'path' portion of a URL. For example, if the URL is
876
     * http://www.example.org:447/my/file/is/here.txt?really=1 then this will
877
     * return '/my/file/is/here.txt'.
878
     *
879
     * By default the path includes slash-arguments (for example,
880
     * '/myfile.php/extra/arguments') so it is what you would expect from a
881
     * URL path. If you don't want this behaviour, you can opt to exclude the
882
     * slash arguments. (Be careful: if the $CFG variable slasharguments is
883
     * disabled, these URLs will have a different format and you may need to
884
     * look at the 'file' parameter too.)
885
     *
886
     * @param bool $includeslashargument If true, includes slash arguments
887
     * @return string Path of URL
888
     */
889
    public function get_path($includeslashargument = true) {
890
        return $this->path . ($includeslashargument ? $this->slashargument : '');
891
    }
892
 
893
    /**
894
     * Returns a given parameter value from the URL.
895
     *
896
     * @param string $name Name of parameter
897
     * @return array|string|null Value of parameter or null if not set.
898
     */
899
    public function get_param($name) {
900
        if (array_key_exists($name, $this->params)) {
901
            return $this->params[$name];
902
        } else {
903
            return null;
904
        }
905
    }
906
 
907
    /**
908
     * Returns the 'scheme' portion of a URL. For example, if the URL is
909
     * http://www.example.org:447/my/file/is/here.txt?really=1 then this will
910
     * return 'http' (without the colon).
911
     *
912
     * @return string Scheme of the URL.
913
     */
914
    public function get_scheme() {
915
        return $this->scheme;
916
    }
917
 
918
    /**
919
     * Returns the 'host' portion of a URL. For example, if the URL is
920
     * http://www.example.org:447/my/file/is/here.txt?really=1 then this will
921
     * return 'www.example.org'.
922
     *
923
     * @return string Host of the URL.
924
     */
925
    public function get_host() {
926
        return $this->host;
927
    }
928
 
929
    /**
930
     * Returns the 'port' portion of a URL. For example, if the URL is
931
     * http://www.example.org:447/my/file/is/here.txt?really=1 then this will
932
     * return '447'.
933
     *
934
     * @return string Port of the URL.
935
     */
936
    public function get_port() {
937
        return $this->port;
938
    }
939
 
940
    /**
941
     * Returns the 'slashargument' portion of a URL. For example, if the URL is
942
     * http://www.example.org.com/pluginfile.php/1/core_admin/logocompact/ then this will
943
     * return '1/core_admin/logocompact/'.
944
     *
945
     * @return string Slash argument as string.
946
     */
947
    public function get_slashargument(): string {
948
        return $this->slashargument;
949
    }
950
}
951
 
952
// Alias this class to the old name.
953
// This file will be autoloaded by the legacyclasses autoload system.
954
// In future all uses of this class will be corrected and the legacy references will be removed.
955
class_alias(url::class, \moodle_url::class);