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 GuzzleHttp\Psr7\Uri;
20
 
21
/**
22
 * Tests for \core\url.
23
 *
24
 * @package   core
25
 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
26
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 * @covers    \core\url
28
 */
29
final class url_test extends \advanced_testcase {
30
    /**
31
     * Test basic url construction.
32
     */
33
    public function test_constructor(): void {
34
        global $CFG;
35
 
36
        $url = new url('/index.php');
37
        $this->assertSame($CFG->wwwroot . '/index.php', $url->out());
38
 
39
        $url = new url('/index.php', []);
40
        $this->assertSame($CFG->wwwroot . '/index.php', $url->out());
41
 
42
        $url = new url('/index.php', ['id' => 2]);
43
        $this->assertSame($CFG->wwwroot . '/index.php?id=2', $url->out());
44
 
45
        $url = new url('/index.php', ['id' => 'two']);
46
        $this->assertSame($CFG->wwwroot . '/index.php?id=two', $url->out());
47
 
48
        $url = new url('/index.php', ['id' => 1, 'cid' => '2']);
49
        $this->assertSame($CFG->wwwroot . '/index.php?id=1&amp;cid=2', $url->out());
50
        $this->assertSame($CFG->wwwroot . '/index.php?id=1&cid=2', $url->out(false));
51
 
52
        $url = new url('/index.php', null, 'test');
53
        $this->assertSame($CFG->wwwroot . '/index.php#test', $url->out());
54
 
55
        $url = new url('/index.php', ['id' => 2], 'test');
56
        $this->assertSame($CFG->wwwroot . '/index.php?id=2#test', $url->out());
57
    }
58
 
59
    /**
60
     * Tests url::get_path().
61
     */
62
    public function test_get_path(): void {
63
        $url = new url('http://www.example.org:447/my/file/is/here.txt?really=1');
64
        $this->assertSame('/my/file/is/here.txt', $url->get_path());
65
 
66
        $url = new url('http://www.example.org/');
67
        $this->assertSame('/', $url->get_path());
68
 
69
        $url = new url('http://www.example.org/pluginfile.php/slash/arguments');
70
        $this->assertSame('/pluginfile.php/slash/arguments', $url->get_path());
71
        $this->assertSame('/pluginfile.php', $url->get_path(false));
72
    }
73
 
74
    public function test_round_trip(): void {
75
        $strurl = 'http://moodle.org/course/view.php?id=5';
76
        $url = new url($strurl);
77
        $this->assertSame($strurl, $url->out(false));
78
 
79
        $strurl = 'http://moodle.org/user/index.php?contextid=53&sifirst=M&silast=D';
80
        $url = new url($strurl);
81
        $this->assertSame($strurl, $url->out(false));
82
    }
83
 
84
    /**
85
     * Test Moodle URL objects created with a param with empty value.
86
     */
87
    public function test_empty_param_values(): void {
88
        $strurl = 'http://moodle.org/course/view.php?id=0';
89
        $url = new url($strurl, ['id' => 0]);
90
        $this->assertSame($strurl, $url->out(false));
91
 
92
        $strurl = 'http://moodle.org/course/view.php?id';
93
        $url = new url($strurl, ['id' => false]);
94
        $this->assertSame($strurl, $url->out(false));
95
 
96
        $strurl = 'http://moodle.org/course/view.php?id';
97
        $url = new url($strurl, ['id' => null]);
98
        $this->assertSame($strurl, $url->out(false));
99
 
100
        $strurl = 'http://moodle.org/course/view.php?id';
101
        $url = new url($strurl, ['id' => '']);
102
        $this->assertSame($strurl, $url->out(false));
103
 
104
        $strurl = 'http://moodle.org/course/view.php?id';
105
        $url = new url($strurl);
106
        $this->assertSame($strurl, $url->out(false));
107
    }
108
 
109
    /**
110
     * Test set good scheme on Moodle URL objects.
111
     */
112
    public function test_set_good_scheme(): void {
113
        $url = new url('http://moodle.org/foo/bar');
114
        $url->set_scheme('myscheme');
115
        $this->assertSame('myscheme://moodle.org/foo/bar', $url->out());
116
    }
117
 
118
    /**
119
     * Test set bad scheme on Moodle URL objects.
120
     */
121
    public function test_set_bad_scheme(): void {
122
        $url = new url('http://moodle.org/foo/bar');
123
        $this->expectException(\coding_exception::class);
124
        $url->set_scheme('not a valid $ scheme');
125
    }
126
 
127
    public function test_round_trip_array_params(): void {
128
        $strurl = 'http://example.com/?a%5B1%5D=1&a%5B2%5D=2';
129
        $url = new url($strurl);
130
        $this->assertSame($strurl, $url->out(false));
131
 
132
        $url = new url('http://example.com/?a[1]=1&a[2]=2');
133
        $this->assertSame($strurl, $url->out(false));
134
 
135
        // For un-keyed array params, we expect 0..n keys to be returned.
136
        $strurl = 'http://example.com/?a%5B0%5D=0&a%5B1%5D=1';
137
        $url = new url('http://example.com/?a[]=0&a[]=1');
138
        $this->assertSame($strurl, $url->out(false));
139
    }
140
 
141
    public function test_compare_url(): void {
142
        $url1 = new url('index.php', ['var1' => 1, 'var2' => 2]);
143
        $url2 = new url('index2.php', ['var1' => 1, 'var2' => 2, 'var3' => 3]);
144
 
145
        $this->assertFalse($url1->compare($url2, URL_MATCH_BASE));
146
        $this->assertFalse($url1->compare($url2, URL_MATCH_PARAMS));
147
        $this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
148
 
149
        $url2 = new url('index.php', ['var1' => 1, 'var3' => 3]);
150
 
151
        $this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
152
        $this->assertFalse($url1->compare($url2, URL_MATCH_PARAMS));
153
        $this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
154
 
155
        $url2 = new url('index.php', ['var1' => 1, 'var2' => 2, 'var3' => 3]);
156
 
157
        $this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
158
        $this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
159
        $this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
160
 
161
        $url2 = new url('index.php', ['var2' => 2, 'var1' => 1]);
162
 
163
        $this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
164
        $this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
165
        $this->assertTrue($url1->compare($url2, URL_MATCH_EXACT));
166
 
167
        $url1->set_anchor('test');
168
        $this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
169
        $this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
170
        $this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
171
 
172
        $url2->set_anchor('test');
173
        $this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
174
        $this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
175
        $this->assertTrue($url1->compare($url2, URL_MATCH_EXACT));
176
    }
177
 
178
    public function test_out_as_local_url_error(): void {
179
        $url2 = new url('http://www.google.com/lib/tests/weblib_test.php');
180
        $this->expectException(\coding_exception::class);
181
        $url2->out_as_local_url();
182
    }
183
 
184
    /**
185
     * You should get error with modified url
186
     */
187
    public function test_modified_url_out_as_local_url_error(): void {
188
        global $CFG;
189
 
190
        $modifiedurl = $CFG->wwwroot . '1';
191
        $url3 = new url($modifiedurl . '/login/profile.php');
192
        $this->expectException(\coding_exception::class);
193
        $url3->out_as_local_url();
194
    }
195
 
196
    /**
197
     * Try get local url from external https url and you should get error
198
     */
199
    public function test_https_out_as_local_url_error(): void {
200
        $url4 = new url('https://www.google.com/lib/tests/weblib_test.php');
201
        $this->expectException(\coding_exception::class);
202
        $url4->out_as_local_url();
203
    }
204
 
205
    public function test_get_scheme(): void {
206
        // Should return the scheme only.
207
        $url = new url('http://www.example.org:447/my/file/is/here.txt?really=1');
208
        $this->assertSame('http', $url->get_scheme());
209
 
210
        // Should work for secure URLs.
211
        $url = new url('https://www.example.org:447/my/file/is/here.txt?really=1');
212
        $this->assertSame('https', $url->get_scheme());
213
 
214
        // Should return an empty string if no scheme is specified.
215
        $url = new url('www.example.org:447/my/file/is/here.txt?really=1');
216
        $this->assertSame('', $url->get_scheme());
217
    }
218
 
219
    public function test_get_host(): void {
220
        // Should return the host part only.
221
        $url = new url('http://www.example.org:447/my/file/is/here.txt?really=1');
222
        $this->assertSame('www.example.org', $url->get_host());
223
    }
224
 
225
    public function test_get_port(): void {
226
        // Should return the port if one provided.
227
        $url = new url('http://www.example.org:447/my/file/is/here.txt?really=1');
228
        $this->assertSame(447, $url->get_port());
229
 
230
        // Should return an empty string if port not specified.
231
        $url = new url('http://www.example.org/some/path/here.php');
232
        $this->assertSame('', $url->get_port());
233
    }
234
 
235
    /**
236
     * Test exporting params for templates.
237
     *
238
     * @dataProvider export_params_for_template_provider
239
     * @param string $url URL with params to test.
240
     * @param array $expected The expected result.
241
     */
242
    public function test_export_params_for_template(string $url, array $expected): void {
243
        // Should return params in the URL.
244
        $moodleurl = new url($url);
245
        $this->assertSame($expected, $moodleurl->export_params_for_template());
246
    }
247
 
248
    /**
249
     * Data provider for export_params_for_template tests.
250
     *
251
     * @return array[] the array of test data.
252
     */
253
    public static function export_params_for_template_provider(): array {
254
        $baseurl = "http://example.com";
255
        return [
256
                'With indexed array params' => [
257
                    'url' => "@{$baseurl}/?tags[0]=123&tags[1]=456",
258
                    'expected' => [
259
 
260
                        1 => ['name' => 'tags[1]', 'value' => '456'],
261
                    ],
262
                ],
263
                'Without indexed array params' => [
264
                    'url' => "@{$baseurl}/?tags[]=123&tags[]=456",
265
                    'expected' => [
266
 
267
                        1 => ['name' => 'tags[1]', 'value' => '456'],
268
                    ],
269
                ],
270
                'with no params' => [
271
                    'url' => "@{$baseurl}/",
272
                    'expected' => [],
273
                ],
274
                'with no array params' => [
275
                    'url' => "@{$baseurl}/?param1=1&param2=2&param3=3",
276
                    'expected' => [
277
 
278
                        1 => ['name' => 'param2', 'value' => '2'],
279
                        2 => ['name' => 'param3', 'value' => '3'],
280
                    ],
281
                ],
282
                'array embedded with other params' => [
283
                    'url' => "@{$baseurl}/?param1=1&tags[0]=123&tags[1]=456&param2=2&param3=3",
284
                    'expected' => [
285
 
286
                        1 => ['name' => 'tags[0]', 'value' => '123'],
287
                        2 => ['name' => 'tags[1]', 'value' => '456'],
288
                        3 => ['name' => 'param2', 'value' => '2'],
289
                        4 => ['name' => 'param3', 'value' => '3'],
290
                    ],
291
                ],
292
                'multi level array embedded with other params' => [
293
                    'url' => "@{$baseurl}/?param1=1&tags[0][0]=123&tags[0][1]=456&param2=2&param3=3",
294
                    'expected' => [
295
 
296
                        1 => ['name' => 'tags[0][0]', 'value' => '123'],
297
                        2 => ['name' => 'tags[0][1]', 'value' => '456'],
298
                        3 => ['name' => 'param2', 'value' => '2'],
299
                        4 => ['name' => 'param3', 'value' => '3'],
300
                    ],
301
                ],
302
                'params with array at the end' => [
303
                    'url' => "@{$baseurl}/?param1=1&tags[]=123&tags[]=456",
304
                    'expected' => [
305
 
306
                        1 => ['name' => 'tags[0]', 'value' => '123'],
307
                        2 => ['name' => 'tags[1]', 'value' => '456'],
308
                    ],
309
                ],
310
                'equals sign encoded in a string in params' => [
311
                    'url' => "@{$baseurl}/?param1=https://example.moodle.net?test=2",
312
                    'expected' => [
313
 
314
                    ],
315
                ],
316
        ];
317
    }
318
 
319
    /**
320
     * Test the make_pluginfile_url function.
321
     *
322
     * @dataProvider make_pluginfile_url_provider
323
     * @param   bool    $slashargs
324
     * @param   array   $args Args to be provided to make_pluginfile_url
325
     * @param   string  $expected The expected result
326
     */
327
    public function test_make_pluginfile_url($slashargs, $args, $expected): void {
328
        global $CFG;
329
 
330
        $this->resetAfterTest();
331
 
332
        $CFG->slasharguments = $slashargs;
333
        $url = call_user_func_array([url::class, 'make_pluginfile_url'], $args);
334
        $this->assertMatchesRegularExpression($expected, $url->out(true));
335
    }
336
 
337
    /**
338
     * Test the get_slashargument method.
339
     */
340
    public function test_get_slashargument(): void {
341
        $this->resetAfterTest();
342
 
343
        $url = new url('/pluginfile.php/14/user/private/capybara.png');
344
        $this->assertEquals('/14/user/private/capybara.png', $url->get_slashargument());
345
 
346
        $url = new url('/image/capybara.png');
347
        $this->assertEmpty($url->get_slashargument());
348
    }
349
 
350
    /**
351
     * Data provider for make_pluginfile_url tests.
352
     *
353
     * @return  array[]
354
     */
355
    public static function make_pluginfile_url_provider(): array {
356
        $baseurl = "https://www.example.com/moodle/pluginfile.php";
357
        $tokenbaseurl = "https://www.example.com/moodle/tokenpluginfile.php";
358
        return [
359
            'Standard with slashargs' => [
360
                'slashargs' => true,
361
                'args' => [
362
                    1,
363
                    'mod_forum',
364
                    'posts',
365
                    422,
366
                    '/my/location/',
367
                    'file.png',
368
                ],
369
                'expected' => "@{$baseurl}/1/mod_forum/posts/422/my/location/file.png@",
370
            ],
371
            'Standard without slashargs' => [
372
                'slashargs' => false,
373
                'args' => [
374
                    1,
375
                    'mod_forum',
376
                    'posts',
377
                    422,
378
                    '/my/location/',
379
                    'file.png',
380
                ],
381
                'expected' => "@{$baseurl}\?file=%2F1%2Fmod_forum%2Fposts%2F422%2Fmy%2Flocation%2Ffile.png@",
382
            ],
383
            'Token included with slashargs' => [
384
                'slashargs' => true,
385
                'args' => [
386
                    1,
387
                    'mod_forum',
388
                    'posts',
389
                    422,
390
                    '/my/location/',
391
                    'file.png',
392
                    false,
393
                    true,
394
                ],
395
                'expected' => "@{$tokenbaseurl}/[^/]*/1/mod_forum/posts/422/my/location/file.png@",
396
            ],
397
            'Token included without slashargs' => [
398
                'slashargs' => false,
399
                'args' => [
400
                    1,
401
                    'mod_forum',
402
                    'posts',
403
                    422,
404
                    '/my/location/',
405
                    'file.png',
406
                    false,
407
                    true,
408
                ],
409
                'expected' =>
410
                    "@{$tokenbaseurl}\?file=%2F1%2Fmod_forum%2Fposts%2F422%2Fmy%2Flocation%2Ffile.png&amp;token=[a-z0-9]*@",
411
            ],
412
        ];
413
    }
414
 
415
    public function test_from_uri(): void {
416
        global $CFG;
417
 
418
        $uri = new Uri('http://www.example.org:447/my/file/is/here.txt?really=1');
419
        $url = url::from_uri($uri);
420
        $this->assertSame('http://www.example.org:447/my/file/is/here.txt?really=1', $url->out(false));
421
        $this->assertEquals(1, $url->param('really'));
422
 
423
        $uri = new Uri('https://www.example.org/my/file/is/here.txt?really=1');
424
        $url = url::from_uri($uri);
425
        $this->assertSame('https://www.example.org/my/file/is/here.txt?really=1', $url->out(false));
426
        $this->assertEquals(1, $url->param('really'));
427
 
428
        // Multiple params.
429
        $uri = new Uri('https://www.example.org/my/file/is/here.txt?really=1&another=2&&more=3&moar=4');
430
        $url = url::from_uri($uri);
431
        $this->assertSame('https://www.example.org/my/file/is/here.txt?really=1&another=2&more=3&moar=4', $url->out(false));
432
        $this->assertEquals(1, $url->param('really'));
433
        $this->assertEquals(2, $url->param('another'));
434
        $this->assertEquals(3, $url->param('more'));
435
        $this->assertEquals(4, $url->param('moar'));
436
 
437
        // Anchors.
438
        $uri = new Uri("{$CFG->wwwroot}/course/view/#section-1");
439
        $url = url::from_uri($uri);
440
        $this->assertSame("{$CFG->wwwroot}/course/view/#section-1", $url->out(false));
441
        $this->assertEmpty($url->params());
442
    }
443
 
444
    /**
445
     * Test url fragment parsing.
446
     *
447
     * @dataProvider url_fragment_parsing_provider
448
     */
449
    public function test_url_fragment_parsing(string $fragment, string $expected): void {
450
        $url = new url('/index.php', null, $fragment);
451
 
452
        // Test the encoded fragment.
453
        $this->assertEquals(
454
            "#{$expected}",
455
            $url->get_encoded_anchor(),
456
        );
457
 
458
        // Test the value of ->raw_out() with escaping enabled.
459
        $parts = parse_url($url->raw_out(true), PHP_URL_FRAGMENT);
460
        $this->assertEquals($expected, parse_url($url->raw_out(true), PHP_URL_FRAGMENT));
461
 
462
        // Test the value of ->raw_out() with escaping disabled.
463
        $parts = parse_url($url->raw_out(false));
464
        $this->assertEquals($expected, $parts['fragment']);
465
 
466
        // Test the value of ->out() with escaping enabled.
467
        $parts = parse_url($url->out(true));
468
        $this->assertEquals($expected, $parts['fragment']);
469
 
470
        // Test the value of ->out() with escaping disabled.
471
        $parts = parse_url($url->out(false));
472
        $this->assertEquals($expected, $parts['fragment']);
473
    }
474
 
475
    /**
476
     * Data provider for url_fragment_parsing tests.
477
     *
478
     * @return array
479
     */
480
    public static function url_fragment_parsing_provider(): array {
481
        return [
482
            'Simple fragment' => ['test', 'test'],
483
            // RFC 3986 allows the following characters in a fragment without them being encoded:
484
            // pct-encoded: "%" HEXDIG HEXDIG
485
            // unreserved:  ALPHA / DIGIT / "-" / "." / "_" / "~" /
486
            // sub-delims:  "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" / ":" / "@"
487
            // fragment:    "/" / "?"
488
            //
489
            // These should not be encoded in the fragment unless they were already encoded.
490
            'Fragment with RFC3986 characters' => [
491
                'test-._~!$&\'()*+,;=:@/?',
492
                'test-._~!$&\'()*+,;=:@/?',
493
            ],
494
            'Contains % without HEXDIG HEXDIG' => [
495
                '%Percent',
496
                '%25Percent',
497
            ],
498
            'Contains multiple %' => [
499
                // A % followed by a valid pct-encoded followed by two more %%.
500
                '%%23%%',
501
                '%25%23%25%25',
502
            ],
503
            'Fragment with already-encoded RFC3986 characters' => [
504
                rawurlencode('test-._~!$&\'()*+,;=:@/?'),
505
                rawurlencode('test-._~!$&\'()*+,;=:@/?'),
506
            ],
507
            'Fragment with encoded slashes' => ['test%2fwith%2fencoded%2fslashes', 'test%2fwith%2fencoded%2fslashes'],
508
            'Fragment with encoded characters' => ['test%20with%20encoded%20characters', 'test%20with%20encoded%20characters'],
509
 
510
            // The following are examples which _should_ become encoded.
511
            'Spaces become encoded' => ['test with spaces', 'test%20with%20spaces'],
512
            'Quotes become encoded' => ['test with "quotes"', 'test%20with%20%22quotes%22'],
513
        ];
514
    }
515
 
516
    /**
517
     * Test the coding exceptions when returning URL as relative path from $CFG->wwwroot.
518
     *
519
     * @param url $url The URL pointing to a web resource.
520
     * @param string $exmessage The expected output URL.
521
     * @dataProvider out_as_local_url_coding_exception_provider
522
     */
523
    public function test_out_as_local_url_coding_exception(url $url, string $exmessage): void {
524
        $this->expectException(\coding_exception::class);
525
        $this->expectExceptionMessage($exmessage);
526
        $localurl = $url->out_as_local_url();
527
    }
528
 
529
    /**
530
     * Data provider for throwing coding exceptions in <u>url::out_as_local_url()</u>.
531
     *
532
     * @return array
533
     */
534
    public static function out_as_local_url_coding_exception_provider(): array {
535
        return [
536
            'Google Maps CDN (HTTPS)' => [
537
                new url('https://maps.googleapis.com/maps/api/js', ['key' => 'googlemapkey3', 'sensor' => 'false']),
538
                'Coding error detected, it must be fixed by a programmer: out_as_local_url called on a non-local URL',
539
            ],
540
            'Google Maps CDN (HTTP)' => [
541
                new url('http://maps.googleapis.com/maps/api/js', ['key' => 'googlemapkey3', 'sensor' => 'false']),
542
                'Coding error detected, it must be fixed by a programmer: out_as_local_url called on a non-local URL',
543
            ],
544
        ];
545
    }
546
 
547
    /**
548
     * Test URL as relative path from $CFG->wwwroot.
549
     *
550
     * @param url $url The URL pointing to a web resource.
551
     * @param string $expected The expected local URL.
552
     * @param string|null $wwwroot
553
     * @dataProvider out_as_local_url_provider
554
     */
555
    public function test_out_as_local_url(
556
        url $url,
557
        string $expected,
558
        ?string $wwwroot = null,
559
    ): void {
560
        global $CFG;
561
 
562
        if ($wwwroot !== null) {
563
            $CFG->wwwroot = $wwwroot;
564
            $this->resetAfterTest(true);
565
        }
566
        $this->assertEquals($expected, $url->out_as_local_url(false));
567
    }
568
 
569
    /**
570
     * Data provider for returning local paths via <u>url::out_as_local_url()</u>.
571
     *
572
     * @return array
573
     */
574
    public static function out_as_local_url_provider(): array {
575
        global $CFG;
576
        $wwwroot = rtrim($CFG->wwwroot, '/');
577
        $httpswwwroot = str_replace('https://', 'http://', $CFG->wwwroot);
578
 
579
        return [
580
            'HTTP URL' => [
581
                new url("{$wwwroot}/lib/tests/weblib_test.php"),
582
                '/lib/tests/weblib_test.php',
583
                $wwwroot,
584
            ],
585
            'HTTPS URL' => [
586
                new url("{$httpswwwroot}/lib/tests/weblib_test.php"),
587
                '/lib/tests/weblib_test.php',
588
                $httpswwwroot,
589
            ],
590
            'Plain wwwroot' => [
591
                new url($CFG->wwwroot),
592
                '',
593
            ],
594
            'wwwroot With trailing /' => [
595
                new url($CFG->wwwroot . '/'),
596
                '/',
597
            ],
598
            'Environment XML file' => [
599
                new url('/admin/environment.xml'),
600
                '/admin/environment.xml',
601
            ],
602
            'H5P JS internal resource' => [
603
                new url('/h5p/js/embed.js'),
604
                '/h5p/js/embed.js',
605
            ],
606
            'A Moodle JS resource using the full path including the proper JS Handler' => [
607
                new url($wwwroot . '/lib/javascript.php/1/lib/editor/tiny/js/tinymce/tinymce.js'),
608
                '/lib/javascript.php/1/lib/editor/tiny/js/tinymce/tinymce.js',
609
            ],
610
        ];
611
    }
612
 
613
    /**
614
     * Test URL as relative path from $CFG->wwwroot.
615
     *
616
     * @param url $url The URL pointing to a web resource.
617
     * @param bool $expected The expected result.
618
     * @dataProvider is_local_url_provider
619
     */
620
    public function test_is_local_url(url $url, bool $expected): void {
621
        $this->assertEquals($expected, $url->is_local_url(), "'{$url}' is not a local URL!");
622
    }
623
 
624
    /**
625
     * Data provider for testing <u>url::is_local_url()</u>.
626
     *
627
     * @return array
628
     */
629
    public static function is_local_url_provider(): array {
630
        global $CFG;
631
        $wwwroot = rtrim($CFG->wwwroot, '/');
632
 
633
        return [
634
            'Google Maps CDN (HTTPS)' => [
635
                new url('https://maps.googleapis.com/maps/api/js', ['key' => 'googlemapkey3', 'sensor' => 'false']),
636
                false,
637
            ],
638
            'Google Maps CDN (HTTP)' => [
639
                new url('http://maps.googleapis.com/maps/api/js', ['key' => 'googlemapkey3', 'sensor' => 'false']),
640
                false,
641
            ],
642
            'wwwroot' => [
643
                new url($wwwroot),
644
                true,
645
            ],
646
            'wwwroot/' => [
647
                new url($wwwroot . '/'),
648
                true,
649
            ],
650
            'Environment XML file' => [
651
                new url('/admin/environment.xml'),
652
                true,
653
            ],
654
            'H5P JS internal resource' => [
655
                new url('/h5p/js/embed.js'),
656
                true,
657
            ],
658
        ];
659
    }
660
 
661
    /**
662
     * @dataProvider remove_params_provider
663
     */
664
    public function test_remove_params($params, $remove, $expected): void {
665
        $url = new url('/index.php', $params);
666
        if ($remove !== null) {
667
            $url->remove_params(...$remove);
668
        }
669
        $this->assertSame($expected, $url->params());
670
    }
671
 
672
    public static function remove_params_provider(): array {
673
        return [
674
            [
675
                ['id' => 1, 'cid' => 2, 'sid' => 3],
676
                null,
677
                ['id' => '1', 'cid' => '2', 'sid' => '3'],
678
            ],
679
            [
680
                ['id' => 1, 'cid' => 2, 'sid' => 3],
681
                [],
682
                ['id' => '1', 'cid' => '2', 'sid' => '3'],
683
            ],
684
            [
685
                ['id' => 1, 'cid' => 2, 'sid' => 3],
686
                ['other'],
687
                ['id' => '1', 'cid' => '2', 'sid' => '3'],
688
            ],
689
            [
690
                ['id' => 1, 'cid' => 2, 'sid' => 3],
691
                ['id', 'sid'],
692
                ['cid' => '2'],
693
            ],
694
            [
695
                ['id' => 1, 'cid' => 2, 'sid' => 3],
696
                [['id', 'sid']],
697
                ['cid' => '2'],
698
            ],
699
        ];
700
    }
701
 
702
    /**
703
     * Test that URL routed paths are generated correctly depending on the value of $CFG->routerconfigured.
704
     */
705
    public function test_routed_path(): void {
706
        global $CFG;
707
 
708
        $this->resetAfterTest();
709
 
710
        $CFG->routerconfigured = false;
711
        $url = url::routed_path('/example');
712
        $this->assertSame('/r.php/example', $url->out_as_local_url(false));
713
 
714
        $CFG->routerconfigured = true;
715
        $url = url::routed_path('/example');
716
        $this->assertSame('/example', $url->out_as_local_url(false));
717
    }
718
 
719
    /**
720
     * Provides various urls with multi level array query parameters
721
     *
722
     * @return array
723
     */
724
    public static function multi_level_query_params_provider(): array {
725
        return [
726
            'multi level with integer values' => [
727
                'url' => 'https://example.moodle.net?test[0][0]=1&test[0][1]=0',
728
                'extraparams' => [],
729
                'expectedparams' => ['test' => [0 => ['1', '0']]],
730
                'expectedurlout' => 'https://example.moodle.net?test%5B0%5D%5B0%5D=1&test%5B0%5D%5B1%5D=0',
731
            ],
732
            'multi level with bool-looking string values' => [
733
                'url' => 'https://example.moodle.net?test[0][0]=true&test[0][1]=false',
734
                'extraparams' => [],
735
                // These are actually strings, and should be interpreted as such,
736
                // even if they look like booleans.
737
                'expectedparams' => ['test' => [0 => ['true', 'false']]],
738
                'expectedurlout' => 'https://example.moodle.net?test%5B0%5D%5B0%5D=true&test%5B0%5D%5B1%5D=false',
739
            ],
740
            'multi level with bool params values' => [
741
                'url' => 'https://example.moodle.net',
742
                'extraparams' => ['test' => [0 => [true, false]]],
743
                'expectedparams' => ['test' => [0 => ['1', '']]],
744
                // Bool values get stringified. This means true = 1 and false = ''.
745
                'expectedurlout' => 'https://example.moodle.net?test%5B0%5D%5B0%5D=1&test%5B0%5D%5B1%5D',
746
            ],
747
            'triple level array with string values' => [
748
                'url' => 'https://example.moodle.net?test[0][0][0]=abc&test[0][0][1]=xyz',
749
                'extraparams' => [],
750
                'expectedparams' => ['test' => [0 => [0 => ['abc', 'xyz']]]],
751
                'expectedurlout' => 'https://example.moodle.net?test%5B0%5D%5B0%5D%5B0%5D=abc&test%5B0%5D%5B0%5D%5B1%5D=xyz',
752
            ],
753
            'multi level params with empty arrays as values' => [
754
                'url' => 'https://example.moodle.net',
755
                'extraparams' => ['test' => [[[]], [[], []]]],
756
                'expectedparams' => ['test' => [[[]], [[], []]]],
757
                // Empty arrays don't hold any data; they are just containers.
758
                // So this should not have the param present in the query params.
759
                'expectedurlout' => 'https://example.moodle.net',
760
            ],
761
            'multi level params with non sequential arrays keys' => [
762
                'url' => 'https://example.moodle.net?test[2]=a&test[0]=b',
763
                'extraparams' => [],
764
                'expectedparams' => ['test' => [2 => 'a', 0 => 'b']],
765
                'expectedurlout' => 'https://example.moodle.net?test%5B2%5D=a&test%5B0%5D=b',
766
            ],
767
            'multi level params with string numbers as keys' => [
768
                'url' => 'https://example.moodle.net?test[2]=a&test[0]=b',
769
                'extraparams' => [],
770
                'expectedparams' => ['test' => ['2' => 'a', '0' => 'b']],
771
                'expectedurlout' => 'https://example.moodle.net?test%5B2%5D=a&test%5B0%5D=b',
772
            ],
773
        ];
774
    }
775
 
776
    /**
777
     * Tests url parameter handling where multi level arrays are involved.
778
     *
779
     * @param string $url url string to parse.
780
     * @param array $extraparams extra parameters to pass directly to ->params() function.
781
     * @param array $expectedparams php array of expected parameters expected to be parsed.
782
     * @param string $expectedurlout unescaped url string that is expected when calling ->out() on the url object.
783
     * @dataProvider multi_level_query_params_provider
784
     */
785
    public function test_multi_level_array_query_params(
786
        string $url,
787
        array $extraparams,
788
        array $expectedparams,
789
        string $expectedurlout,
790
    ): void {
791
        $url = new url($url);
792
        $url->params($extraparams);
793
        $this->assertSame($expectedparams, $url->params());
794
        $this->assertSame($expectedurlout, $url->out(false));
795
    }
796
}