Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
1441 ariadna 19
use core\tests\fake_plugins_test_trait;
1 efrain 20
 
21
/**
22
 * Unit tests for (some of) ../moodlelib.php.
23
 *
24
 * @package    core
25
 * @category   phpunit
26
 * @copyright  &copy; 2006 The Open University
27
 * @author     T.J.Hunt@open.ac.uk
28
 * @author     nicolas@moodle.com
29
 */
11 efrain 30
final class moodlelib_test extends \advanced_testcase {
1 efrain 31
 
1441 ariadna 32
    use fake_plugins_test_trait;
33
 
1 efrain 34
    /**
35
     * Define a local decimal separator.
36
     *
37
     * It is not possible to directly change the result of get_string in
38
     * a unit test. Instead, we create a language pack for language 'xx' in
39
     * dataroot and make langconfig.php with the string we need to change.
40
     * The default example separator used here is 'X'; on PHP 5.3 and before this
41
     * must be a single byte character due to PHP bug/limitation in
42
     * number_format, so you can't use UTF-8 characters.
43
     *
44
     * @param string $decsep Separator character. Defaults to `'X'`.
45
     */
46
    protected function define_local_decimal_separator(string $decsep = 'X') {
47
        global $SESSION, $CFG;
48
 
49
        $SESSION->lang = 'xx';
50
        $langconfig = "<?php\n\$string['decsep'] = '$decsep';";
51
        $langfolder = $CFG->dataroot . '/lang/xx';
52
        check_dir_exists($langfolder);
53
        file_put_contents($langfolder . '/langconfig.php', $langconfig);
54
 
55
        // Ensure the new value is picked up and not taken from the cache.
56
        $stringmanager = get_string_manager();
57
        $stringmanager->reset_caches(true);
58
    }
59
 
11 efrain 60
    public function test_cleanremoteaddr(): void {
1 efrain 61
        // IPv4.
62
        $this->assertNull(cleanremoteaddr('1023.121.234.1'));
63
        $this->assertSame('123.121.234.1', cleanremoteaddr('123.121.234.01 '));
64
 
65
        // IPv6.
66
        $this->assertNull(cleanremoteaddr('0:0:0:0:0:0:0:0:0'));
67
        $this->assertNull(cleanremoteaddr('0:0:0:0:0:0:0:abh'));
68
        $this->assertNull(cleanremoteaddr('0:0:0:::0:0:1'));
69
        $this->assertSame('::', cleanremoteaddr('0:0:0:0:0:0:0:0', true));
70
        $this->assertSame('::1:1', cleanremoteaddr('0:0:0:0:0:0:1:1', true));
71
        $this->assertSame('abcd:ef::', cleanremoteaddr('abcd:00ef:0:0:0:0:0:0', true));
72
        $this->assertSame('1::1', cleanremoteaddr('1:0:0:0:0:0:0:1', true));
73
        $this->assertSame('0:0:0:0:0:0:10:1', cleanremoteaddr('::10:1', false));
74
        $this->assertSame('1:1:0:0:0:0:0:0', cleanremoteaddr('01:1::', false));
75
        $this->assertSame('10:0:0:0:0:0:0:10', cleanremoteaddr('10::10', false));
76
        $this->assertSame('::ffff:c0a8:11', cleanremoteaddr('::ffff:192.168.1.1', true));
77
    }
78
 
11 efrain 79
    public function test_address_in_subnet(): void {
1 efrain 80
        // 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask).
81
        $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.1/32'));
82
        $this->assertFalse(address_in_subnet('123.121.23.1', '123.121.23.0/32'));
83
        $this->assertTrue(address_in_subnet('10.10.10.100',  '123.121.23.45/0'));
84
        $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.0/24'));
85
        $this->assertFalse(address_in_subnet('123.121.34.1', '123.121.234.0/24'));
86
        $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.0/30'));
87
        $this->assertFalse(address_in_subnet('123.121.23.8', '123.121.23.0/30'));
88
        $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba/128'));
89
        $this->assertFalse(address_in_subnet('bab:baba::baba', 'bab:baba::cece/128'));
90
        $this->assertTrue(address_in_subnet('baba:baba::baba', 'cece:cece::cece/0'));
91
        $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba/128'));
92
        $this->assertTrue(address_in_subnet('baba:baba::00ba', 'baba:baba::/120'));
93
        $this->assertFalse(address_in_subnet('baba:baba::aba', 'baba:baba::/120'));
94
        $this->assertTrue(address_in_subnet('baba::baba:00ba', 'baba::baba:0/112'));
95
        $this->assertFalse(address_in_subnet('baba::aba:00ba', 'baba::baba:0/112'));
96
        $this->assertFalse(address_in_subnet('aba::baba:0000', 'baba::baba:0/112'));
97
 
98
        // Fixed input.
99
        $this->assertTrue(address_in_subnet('123.121.23.1   ', ' 123.121.23.0 / 24'));
100
        $this->assertTrue(address_in_subnet('::ffff:10.1.1.1', ' 0:0:0:000:0:ffff:a1:10 / 126'));
101
 
102
        // Incorrect input.
103
        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/-2'));
104
        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/64'));
105
        $this->assertFalse(address_in_subnet('123.121.234.x', '123.121.234.1/24'));
106
        $this->assertFalse(address_in_subnet('123.121.234.0', '123.121.234.xx/24'));
107
        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/xx0'));
108
        $this->assertFalse(address_in_subnet('::1', '::aa:0/xx0'));
109
        $this->assertFalse(address_in_subnet('::1', '::aa:0/-5'));
110
        $this->assertFalse(address_in_subnet('::1', '::aa:0/130'));
111
        $this->assertFalse(address_in_subnet('x:1', '::aa:0/130'));
112
        $this->assertFalse(address_in_subnet('::1', '::ax:0/130'));
113
 
114
        // 2: xxx.xxx.xxx.xxx-yyy or  xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group).
115
        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12-14'));
116
        $this->assertTrue(address_in_subnet('123.121.234.13', '123.121.234.12-14'));
117
        $this->assertTrue(address_in_subnet('123.121.234.14', '123.121.234.12-14'));
118
        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.12-14'));
119
        $this->assertFalse(address_in_subnet('123.121.234.20', '123.121.234.12-14'));
120
        $this->assertFalse(address_in_subnet('123.121.23.12', '123.121.234.12-14'));
121
        $this->assertFalse(address_in_subnet('123.12.234.12', '123.121.234.12-14'));
122
        $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba-babe'));
123
        $this->assertTrue(address_in_subnet('baba:baba::babc', 'baba:baba::baba-babe'));
124
        $this->assertTrue(address_in_subnet('baba:baba::babe', 'baba:baba::baba-babe'));
125
        $this->assertFalse(address_in_subnet('bab:baba::bab0', 'bab:baba::baba-babe'));
126
        $this->assertFalse(address_in_subnet('bab:baba::babf', 'bab:baba::baba-babe'));
127
        $this->assertFalse(address_in_subnet('bab:baba::bfbe', 'bab:baba::baba-babe'));
128
        $this->assertFalse(address_in_subnet('bfb:baba::babe', 'bab:baba::baba-babe'));
129
 
130
        // Fixed input.
131
        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12 - 14 '));
132
        $this->assertTrue(address_in_subnet('bab:baba::babe', 'bab:baba::baba - babe  '));
133
 
134
        // Incorrect input.
135
        $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12-234.14'));
136
        $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12-256'));
137
        $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12--256'));
138
 
139
        // 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-).
140
        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12'));
141
        $this->assertFalse(address_in_subnet('123.121.23.12', '123.121.23.13'));
142
        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.'));
143
        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234'));
144
        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121'));
145
        $this->assertTrue(address_in_subnet('123.121.234.12', '123'));
146
        $this->assertFalse(address_in_subnet('123.121.234.1', '12.121.234.'));
147
        $this->assertFalse(address_in_subnet('123.121.234.1', '12.121.234'));
148
        $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:baba::bab'));
149
        $this->assertFalse(address_in_subnet('baba:baba::ba', 'baba:baba::bc'));
150
        $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:baba'));
151
        $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:'));
152
        $this->assertFalse(address_in_subnet('bab:baba::bab', 'baba:'));
153
 
154
        // Multiple subnets.
155
        $this->assertTrue(address_in_subnet('123.121.234.12', '::1/64, 124., 123.121.234.10-30'));
156
        $this->assertTrue(address_in_subnet('124.121.234.12', '::1/64, 124., 123.121.234.10-30'));
157
        $this->assertTrue(address_in_subnet('::2',            '::1/64, 124., 123.121.234.10-30'));
158
        $this->assertFalse(address_in_subnet('12.121.234.12', '::1/64, 124., 123.121.234.10-30'));
159
 
160
        // Other incorrect input.
161
        $this->assertFalse(address_in_subnet('123.123.123.123', ''));
162
    }
163
 
11 efrain 164
    public function test_fix_utf8(): void {
1 efrain 165
        // Make sure valid data including other types is not changed.
166
        $this->assertSame(null, fix_utf8(null));
167
        $this->assertSame(1, fix_utf8(1));
168
        $this->assertSame(1.1, fix_utf8(1.1));
169
        $this->assertSame(true, fix_utf8(true));
170
        $this->assertSame('', fix_utf8(''));
171
        $this->assertSame('abc', fix_utf8('abc'));
172
        $array = array('do', 're', 'mi');
173
        $this->assertSame($array, fix_utf8($array));
174
        $object = new \stdClass();
175
        $object->a = 'aa';
176
        $object->b = 'bb';
177
        $this->assertEquals($object, fix_utf8($object));
178
 
179
        // valid utf8 string
180
        $this->assertSame("žlutý koníček přeskočil potůček \n\t\r", fix_utf8("žlutý koníček přeskočil potůček \n\t\r\0"));
181
 
182
        // Invalid utf8 string.
183
        $this->assertSame('aš', fix_utf8('a'.chr(130).'š'), 'This fails with buggy iconv() when mbstring extenstion is not available as fallback.');
184
        $this->assertSame('Hello ', fix_utf8('Hello ï¿¿'));
185
    }
186
 
11 efrain 187
    public function test_optional_param(): void {
1 efrain 188
        global $CFG;
189
 
190
        $_POST['username'] = 'post_user';
191
        $_GET['username'] = 'get_user';
192
        $this->assertSame($_POST['username'], optional_param('username', 'default_user', PARAM_RAW));
193
 
194
        unset($_POST['username']);
195
        $this->assertSame($_GET['username'], optional_param('username', 'default_user', PARAM_RAW));
196
 
197
        unset($_GET['username']);
198
        $this->assertSame('default_user', optional_param('username', 'default_user', PARAM_RAW));
199
 
200
        // Make sure warning is displayed if array submitted - TODO: throw exception in Moodle 2.3.
201
        $_POST['username'] = array('a'=>'a');
202
        try {
203
            optional_param('username', 'default_user', PARAM_RAW);
204
            $this->fail('coding_exception expected');
205
        } catch (\coding_exception $e) {
206
        }
207
    }
208
 
11 efrain 209
    public function test_optional_param_array(): void {
1 efrain 210
        global $CFG;
211
 
212
        $_POST['username'] = array('a'=>'post_user');
213
        $_GET['username'] = array('a'=>'get_user');
214
        $this->assertSame($_POST['username'], optional_param_array('username', array('a'=>'default_user'), PARAM_RAW));
215
 
216
        unset($_POST['username']);
217
        $this->assertSame($_GET['username'], optional_param_array('username', array('a'=>'default_user'), PARAM_RAW));
218
 
219
        unset($_GET['username']);
220
        $this->assertSame(array('a'=>'default_user'), optional_param_array('username', array('a'=>'default_user'), PARAM_RAW));
221
 
222
        // Do not allow nested arrays.
223
        try {
224
            $_POST['username'] = array('a'=>array('b'=>'post_user'));
225
            optional_param_array('username', array('a'=>'default_user'), PARAM_RAW);
226
            $this->fail('coding_exception expected');
227
        } catch (\coding_exception $ex) {
228
            $this->assertTrue(true);
229
        }
230
 
231
        // Do not allow non-arrays.
232
        $_POST['username'] = 'post_user';
233
        $this->assertSame(array('a'=>'default_user'), optional_param_array('username', array('a'=>'default_user'), PARAM_RAW));
234
        $this->assertDebuggingCalled();
235
 
236
        // Make sure array keys are sanitised.
237
        $_POST['username'] = array('abc123_;-/*-+ '=>'arrggh', 'a1_-'=>'post_user');
238
        $this->assertSame(array('a1_-'=>'post_user'), optional_param_array('username', array(), PARAM_RAW));
239
        $this->assertDebuggingCalled();
240
    }
241
 
11 efrain 242
    public function test_required_param(): void {
1 efrain 243
        $_POST['username'] = 'post_user';
244
        $_GET['username'] = 'get_user';
245
        $this->assertSame('post_user', required_param('username', PARAM_RAW));
246
 
247
        unset($_POST['username']);
248
        $this->assertSame('get_user', required_param('username', PARAM_RAW));
249
 
250
        unset($_GET['username']);
251
        try {
252
            $this->assertSame('default_user', required_param('username', PARAM_RAW));
253
            $this->fail('moodle_exception expected');
254
        } catch (\moodle_exception $ex) {
255
            $this->assertInstanceOf('moodle_exception', $ex);
256
        }
257
 
258
        try {
259
            required_param('', PARAM_RAW);
260
            $this->fail('coding_exception expected');
261
        } catch (\moodle_exception $ex) {
262
        }
263
 
264
        // Make sure warning is displayed if array submitted - TODO: throw exception in Moodle 2.3.
265
        $_POST['username'] = array('a'=>'a');
266
        try {
267
            required_param('username', PARAM_RAW);
268
            $this->fail('coding_exception expected');
269
        } catch (\coding_exception $e) {
270
        }
271
    }
272
 
11 efrain 273
    public function test_required_param_array(): void {
1 efrain 274
        global $CFG;
275
 
276
        $_POST['username'] = array('a'=>'post_user');
277
        $_GET['username'] = array('a'=>'get_user');
278
        $this->assertSame($_POST['username'], required_param_array('username', PARAM_RAW));
279
 
280
        unset($_POST['username']);
281
        $this->assertSame($_GET['username'], required_param_array('username', PARAM_RAW));
282
 
283
        // Do not allow nested arrays.
284
        try {
285
            $_POST['username'] = array('a'=>array('b'=>'post_user'));
286
            required_param_array('username', PARAM_RAW);
287
            $this->fail('coding_exception expected');
288
        } catch (\moodle_exception $ex) {
289
            $this->assertInstanceOf('coding_exception', $ex);
290
        }
291
 
292
        // Do not allow non-arrays.
293
        try {
294
            $_POST['username'] = 'post_user';
295
            required_param_array('username', PARAM_RAW);
296
            $this->fail('moodle_exception expected');
297
        } catch (\moodle_exception $ex) {
298
            $this->assertInstanceOf('moodle_exception', $ex);
299
        }
300
 
301
        // Make sure array keys are sanitised.
302
        $_POST['username'] = array('abc123_;-/*-+ '=>'arrggh', 'a1_-'=>'post_user');
303
        $this->assertSame(array('a1_-'=>'post_user'), required_param_array('username', PARAM_RAW));
304
        $this->assertDebuggingCalled();
305
    }
306
 
307
    /**
308
     * @covers \core\param
309
     * @covers \clean_param
310
     */
11 efrain 311
    public function test_clean_param(): void {
1 efrain 312
        // Forbid objects and arrays.
313
        try {
314
            clean_param(array('x', 'y'), PARAM_RAW);
315
            $this->fail('coding_exception expected');
316
        } catch (\moodle_exception $ex) {
317
            $this->assertInstanceOf('coding_exception', $ex);
318
        }
319
        try {
320
            $param = new \stdClass();
321
            $param->id = 1;
322
            clean_param($param, PARAM_RAW);
323
            $this->fail('coding_exception expected');
324
        } catch (\moodle_exception $ex) {
325
            $this->assertInstanceOf('coding_exception', $ex);
326
        }
327
 
328
        // Require correct type.
329
        try {
330
            clean_param('x', 'xxxxxx');
331
            $this->fail('moodle_exception expected');
332
        } catch (\moodle_exception $ex) {
333
            $this->assertInstanceOf('moodle_exception', $ex);
334
        }
335
    }
336
 
337
    /**
338
     * @covers \core\param
339
     * @covers \clean_param
340
     */
11 efrain 341
    public function test_clean_param_array(): void {
1 efrain 342
        $this->assertSame(array(), clean_param_array(null, PARAM_RAW));
343
        $this->assertSame(array('a', 'b'), clean_param_array(array('a', 'b'), PARAM_RAW));
344
        $this->assertSame(array('a', array('b')), clean_param_array(array('a', array('b')), PARAM_RAW, true));
345
 
346
        // Require correct type.
347
        try {
348
            clean_param_array(array('x'), 'xxxxxx');
349
            $this->fail('moodle_exception expected');
350
        } catch (\moodle_exception $ex) {
351
            $this->assertInstanceOf('moodle_exception', $ex);
352
        }
353
 
354
        try {
355
            clean_param_array(array('x', array('y')), PARAM_RAW);
356
            $this->fail('coding_exception expected');
357
        } catch (\moodle_exception $ex) {
358
            $this->assertInstanceOf('coding_exception', $ex);
359
        }
360
 
361
        // Test recursive.
362
    }
363
 
364
    /**
365
     * @covers \core\param
366
     * @covers \clean_param
367
     */
11 efrain 368
    public function test_clean_param_raw(): void {
1 efrain 369
        $this->assertSame(
370
            '#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)',
371
            clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_RAW));
372
        $this->assertSame(null, clean_param(null, PARAM_RAW));
373
    }
374
 
375
    /**
376
     * @covers \core\param
377
     * @covers \clean_param
378
     */
11 efrain 379
    public function test_clean_param_trim(): void {
1 efrain 380
        $this->assertSame('Frog toad', clean_param("   Frog toad   \r\n  ", PARAM_RAW_TRIMMED));
381
        $this->assertSame('', clean_param(null, PARAM_RAW_TRIMMED));
382
    }
383
 
384
    /**
385
     * @covers \core\param
386
     * @covers \clean_param
387
     */
11 efrain 388
    public function test_clean_param_clean(): void {
1 efrain 389
        // PARAM_CLEAN is an ugly hack, do not use in new code (skodak),
390
        // instead use more specific type, or submit sothing that can be verified properly.
391
        $this->assertSame('xx', clean_param('xx<script>', PARAM_CLEAN));
392
        $this->assertSame('', clean_param(null, PARAM_CLEAN));
393
        $this->assertSame('', clean_param(null, PARAM_CLEANHTML));
394
    }
395
 
396
    /**
397
     * @covers \core\param
398
     * @covers \clean_param
399
     */
11 efrain 400
    public function test_clean_param_alpha(): void {
1 efrain 401
        $this->assertSame('DSFMOSDJ', clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_ALPHA));
402
        $this->assertSame('', clean_param(null, PARAM_ALPHA));
403
    }
404
 
405
    /**
406
     * @covers \core\param
407
     * @covers \clean_param
408
     */
11 efrain 409
    public function test_clean_param_alphanum(): void {
1 efrain 410
        $this->assertSame('978942897DSFMOSDJ', clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_ALPHANUM));
411
        $this->assertSame('', clean_param(null, PARAM_ALPHANUM));
412
    }
413
 
414
    /**
415
     * @covers \core\param
416
     * @covers \clean_param
417
     */
11 efrain 418
    public function test_clean_param_alphaext(): void {
1 efrain 419
        $this->assertSame('DSFMOSDJ', clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_ALPHAEXT));
420
        $this->assertSame('', clean_param(null, PARAM_ALPHAEXT));
421
    }
422
 
423
    /**
424
     * @covers \core\param
425
     * @covers \clean_param
426
     */
11 efrain 427
    public function test_clean_param_sequence(): void {
1 efrain 428
        $this->assertSame(',9789,42897', clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_SEQUENCE));
429
        $this->assertSame('', clean_param(null, PARAM_SEQUENCE));
430
    }
431
 
432
    /**
433
     * @covers \core\param
434
     * @covers \clean_param
435
     */
11 efrain 436
    public function test_clean_param_component(): void {
1 efrain 437
        // Please note the cleaning of component names is very strict, no guessing here.
438
        $this->assertSame('mod_forum', clean_param('mod_forum', PARAM_COMPONENT));
439
        $this->assertSame('block_online_users', clean_param('block_online_users', PARAM_COMPONENT));
440
        $this->assertSame('block_blond_online_users', clean_param('block_blond_online_users', PARAM_COMPONENT));
441
        $this->assertSame('mod_something2', clean_param('mod_something2', PARAM_COMPONENT));
442
        $this->assertSame('forum', clean_param('forum', PARAM_COMPONENT));
443
        $this->assertSame('user', clean_param('user', PARAM_COMPONENT));
444
        $this->assertSame('rating', clean_param('rating', PARAM_COMPONENT));
445
        $this->assertSame('feedback360', clean_param('feedback360', PARAM_COMPONENT));
446
        $this->assertSame('mod_feedback360', clean_param('mod_feedback360', PARAM_COMPONENT));
447
        $this->assertSame('', clean_param('mod_2something', PARAM_COMPONENT));
448
        $this->assertSame('', clean_param('2mod_something', PARAM_COMPONENT));
449
        $this->assertSame('', clean_param('mod_something_xx', PARAM_COMPONENT));
450
        $this->assertSame('', clean_param('auth_something__xx', PARAM_COMPONENT));
451
        $this->assertSame('', clean_param('mod_Something', PARAM_COMPONENT));
452
        $this->assertSame('', clean_param('mod_somethíng', PARAM_COMPONENT));
453
        $this->assertSame('', clean_param('mod__something', PARAM_COMPONENT));
454
        $this->assertSame('', clean_param('auth_xx-yy', PARAM_COMPONENT));
455
        $this->assertSame('', clean_param('_auth_xx', PARAM_COMPONENT));
456
        $this->assertSame('a2uth_xx', clean_param('a2uth_xx', PARAM_COMPONENT));
457
        $this->assertSame('', clean_param('auth_xx_', PARAM_COMPONENT));
458
        $this->assertSame('', clean_param('auth_xx.old', PARAM_COMPONENT));
459
        $this->assertSame('', clean_param('_user', PARAM_COMPONENT));
460
        $this->assertSame('', clean_param('2rating', PARAM_COMPONENT));
461
        $this->assertSame('', clean_param('user_', PARAM_COMPONENT));
462
        $this->assertSame('', clean_param(null, PARAM_COMPONENT));
463
    }
464
 
465
    /**
466
     * @covers \core\param
467
     * @covers \clean_param
468
     */
11 efrain 469
    public function test_clean_param_localisedfloat(): void {
1 efrain 470
 
471
        $this->assertSame(0.5, clean_param('0.5', PARAM_LOCALISEDFLOAT));
472
        $this->assertSame(false, clean_param('0X5', PARAM_LOCALISEDFLOAT));
473
        $this->assertSame(0.5, clean_param('.5', PARAM_LOCALISEDFLOAT));
474
        $this->assertSame(false, clean_param('X5', PARAM_LOCALISEDFLOAT));
475
        $this->assertSame(10.5, clean_param('10.5', PARAM_LOCALISEDFLOAT));
476
        $this->assertSame(false, clean_param('10X5', PARAM_LOCALISEDFLOAT));
477
        $this->assertSame(1000.5, clean_param('1 000.5', PARAM_LOCALISEDFLOAT));
478
        $this->assertSame(false, clean_param('1 000X5', PARAM_LOCALISEDFLOAT));
479
        $this->assertSame(false, clean_param('1.000.5', PARAM_LOCALISEDFLOAT));
480
        $this->assertSame(false, clean_param('1X000X5', PARAM_LOCALISEDFLOAT));
481
        $this->assertSame(false, clean_param('nan', PARAM_LOCALISEDFLOAT));
482
        $this->assertSame(false, clean_param('10.6blah', PARAM_LOCALISEDFLOAT));
483
        $this->assertSame(null, clean_param(null, PARAM_LOCALISEDFLOAT));
484
 
485
        // Tests with a localised decimal separator.
486
        $this->define_local_decimal_separator();
487
 
488
        $this->assertSame(0.5, clean_param('0.5', PARAM_LOCALISEDFLOAT));
489
        $this->assertSame(0.5, clean_param('0X5', PARAM_LOCALISEDFLOAT));
490
        $this->assertSame(0.5, clean_param('.5', PARAM_LOCALISEDFLOAT));
491
        $this->assertSame(0.5, clean_param('X5', PARAM_LOCALISEDFLOAT));
492
        $this->assertSame(10.5, clean_param('10.5', PARAM_LOCALISEDFLOAT));
493
        $this->assertSame(10.5, clean_param('10X5', PARAM_LOCALISEDFLOAT));
494
        $this->assertSame(1000.5, clean_param('1 000.5', PARAM_LOCALISEDFLOAT));
495
        $this->assertSame(1000.5, clean_param('1 000X5', PARAM_LOCALISEDFLOAT));
496
        $this->assertSame(false, clean_param('1.000.5', PARAM_LOCALISEDFLOAT));
497
        $this->assertSame(false, clean_param('1X000X5', PARAM_LOCALISEDFLOAT));
498
        $this->assertSame(false, clean_param('nan', PARAM_LOCALISEDFLOAT));
499
        $this->assertSame(false, clean_param('10X6blah', PARAM_LOCALISEDFLOAT));
500
    }
501
 
11 efrain 502
    public function test_is_valid_plugin_name(): void {
1 efrain 503
        $this->assertTrue(is_valid_plugin_name('forum'));
504
        $this->assertTrue(is_valid_plugin_name('forum2'));
505
        $this->assertTrue(is_valid_plugin_name('feedback360'));
506
        $this->assertTrue(is_valid_plugin_name('online_users'));
507
        $this->assertTrue(is_valid_plugin_name('blond_online_users'));
508
        $this->assertFalse(is_valid_plugin_name('online__users'));
509
        $this->assertFalse(is_valid_plugin_name('forum '));
510
        $this->assertFalse(is_valid_plugin_name('forum.old'));
511
        $this->assertFalse(is_valid_plugin_name('xx-yy'));
512
        $this->assertFalse(is_valid_plugin_name('2xx'));
513
        $this->assertFalse(is_valid_plugin_name('Xx'));
514
        $this->assertFalse(is_valid_plugin_name('_xx'));
515
        $this->assertFalse(is_valid_plugin_name('xx_'));
516
    }
517
 
518
    /**
519
     * @covers \core\param
520
     * @covers \clean_param
521
     */
11 efrain 522
    public function test_clean_param_plugin(): void {
1 efrain 523
        // Please note the cleaning of plugin names is very strict, no guessing here.
524
        $this->assertSame('forum', clean_param('forum', PARAM_PLUGIN));
525
        $this->assertSame('forum2', clean_param('forum2', PARAM_PLUGIN));
526
        $this->assertSame('feedback360', clean_param('feedback360', PARAM_PLUGIN));
527
        $this->assertSame('online_users', clean_param('online_users', PARAM_PLUGIN));
528
        $this->assertSame('blond_online_users', clean_param('blond_online_users', PARAM_PLUGIN));
529
        $this->assertSame('', clean_param('online__users', PARAM_PLUGIN));
530
        $this->assertSame('', clean_param('forum ', PARAM_PLUGIN));
531
        $this->assertSame('', clean_param('forum.old', PARAM_PLUGIN));
532
        $this->assertSame('', clean_param('xx-yy', PARAM_PLUGIN));
533
        $this->assertSame('', clean_param('2xx', PARAM_PLUGIN));
534
        $this->assertSame('', clean_param('Xx', PARAM_PLUGIN));
535
        $this->assertSame('', clean_param('_xx', PARAM_PLUGIN));
536
        $this->assertSame('', clean_param('xx_', PARAM_PLUGIN));
537
        $this->assertSame('', clean_param(null, PARAM_PLUGIN));
538
    }
539
 
540
    /**
541
     * @covers \core\param
542
     * @covers \clean_param
543
     */
11 efrain 544
    public function test_clean_param_area(): void {
1 efrain 545
        // Please note the cleaning of area names is very strict, no guessing here.
546
        $this->assertSame('something', clean_param('something', PARAM_AREA));
547
        $this->assertSame('something2', clean_param('something2', PARAM_AREA));
548
        $this->assertSame('some_thing', clean_param('some_thing', PARAM_AREA));
549
        $this->assertSame('some_thing_xx', clean_param('some_thing_xx', PARAM_AREA));
550
        $this->assertSame('feedback360', clean_param('feedback360', PARAM_AREA));
551
        $this->assertSame('', clean_param('_something', PARAM_AREA));
552
        $this->assertSame('', clean_param('something_', PARAM_AREA));
553
        $this->assertSame('', clean_param('2something', PARAM_AREA));
554
        $this->assertSame('', clean_param('Something', PARAM_AREA));
555
        $this->assertSame('', clean_param('some-thing', PARAM_AREA));
556
        $this->assertSame('', clean_param('somethííng', PARAM_AREA));
557
        $this->assertSame('', clean_param('something.x', PARAM_AREA));
558
        $this->assertSame('', clean_param(null, PARAM_AREA));
559
    }
560
 
561
    /**
562
     * @covers \core\param
563
     * @covers \clean_param
564
     */
11 efrain 565
    public function test_clean_param_text(): void {
1 efrain 566
        // Standard.
567
        $this->assertSame('xx<lang lang="en">aa</lang><lang lang="yy">pp</lang>', clean_param('xx<lang lang="en">aa</lang><lang lang="yy">pp</lang>', PARAM_TEXT));
568
        $this->assertSame('<span lang="en" class="multilang">aa</span><span lang="xy" class="multilang">bb</span>', clean_param('<span lang="en" class="multilang">aa</span><span lang="xy" class="multilang">bb</span>', PARAM_TEXT));
569
        $this->assertSame('xx<lang lang="en">aa'."\n".'</lang><lang lang="yy">pp</lang>', clean_param('xx<lang lang="en">aa'."\n".'</lang><lang lang="yy">pp</lang>', PARAM_TEXT));
570
        // Malformed.
571
        $this->assertSame('<span lang="en" class="multilang">aa</span>', clean_param('<span lang="en" class="multilang">aa</span>', PARAM_TEXT));
572
        $this->assertSame('aa', clean_param('<span lang="en" class="nothing" class="multilang">aa</span>', PARAM_TEXT));
573
        $this->assertSame('aa', clean_param('<lang lang="en" class="multilang">aa</lang>', PARAM_TEXT));
574
        $this->assertSame('aa', clean_param('<lang lang="en!!">aa</lang>', PARAM_TEXT));
575
        $this->assertSame('aa', clean_param('<span lang="en==" class="multilang">aa</span>', PARAM_TEXT));
576
        $this->assertSame('abc', clean_param('a<em>b</em>c', PARAM_TEXT));
577
        $this->assertSame('a>c>', clean_param('a><xx >c>', PARAM_TEXT)); // Standard strip_tags() behaviour.
578
        $this->assertSame('a', clean_param('a<b', PARAM_TEXT));
579
        $this->assertSame('a>b', clean_param('a>b', PARAM_TEXT));
580
        $this->assertSame('<lang lang="en">a>a</lang>', clean_param('<lang lang="en">a>a</lang>', PARAM_TEXT)); // Standard strip_tags() behaviour.
581
        $this->assertSame('a', clean_param('<lang lang="en">a<a</lang>', PARAM_TEXT));
582
        $this->assertSame('<lang lang="en">aa</lang>', clean_param('<lang lang="en">a<br>a</lang>', PARAM_TEXT));
583
        $this->assertSame('', clean_param(null, PARAM_TEXT));
584
    }
585
 
586
    /**
587
     * Data provider for {@see test_clean_param_host}
588
     *
589
     * @return array
590
     */
591
    public static function clean_param_host_provider(): array {
592
        return [
593
            'Valid (low octets)' => ['0.0.0.0', '0.0.0.0'],
594
            'Valid (high octets)' => ['255.255.255.255', '255.255.255.255'],
595
            'Invalid first octet' => ['256.1.1.1', ''],
596
            'Invalid second octet' => ['1.256.1.1', ''],
597
            'Invalid third octet' => ['1.1.256.1', ''],
598
            'Invalid fourth octet' => ['1.1.1.256', ''],
599
            'Valid host' => ['moodle.org', 'moodle.org'],
600
            'Invalid host' => ['.example.com', ''],
601
        ];
602
    }
603
 
604
    /**
605
     * Testing cleaning parameters with PARAM_HOST
606
     *
607
     * @param string $param
608
     * @param string $expected
609
     *
610
     * @dataProvider clean_param_host_provider
611
     *
612
     * @covers \core\param
613
     * @covers \clean_param
614
     */
615
    public function test_clean_param_host(string $param, string $expected): void {
616
        $this->assertEquals($expected, clean_param($param, PARAM_HOST));
617
    }
618
 
619
    /**
620
     * @covers \core\param
621
     * @covers \clean_param
622
     */
11 efrain 623
    public function test_clean_param_url(): void {
1 efrain 624
        // Test PARAM_URL and PARAM_LOCALURL a bit.
625
        // Valid URLs.
626
        $this->assertSame('http://google.com/', clean_param('http://google.com/', PARAM_URL));
627
        $this->assertSame('http://some.very.long.and.silly.domain/with/a/path/', clean_param('http://some.very.long.and.silly.domain/with/a/path/', PARAM_URL));
628
        $this->assertSame('http://localhost/', clean_param('http://localhost/', PARAM_URL));
629
        $this->assertSame('http://0.255.1.1/numericip.php', clean_param('http://0.255.1.1/numericip.php', PARAM_URL));
630
        $this->assertSame('https://google.com/', clean_param('https://google.com/', PARAM_URL));
631
        $this->assertSame('https://some.very.long.and.silly.domain/with/a/path/', clean_param('https://some.very.long.and.silly.domain/with/a/path/', PARAM_URL));
632
        $this->assertSame('https://localhost/', clean_param('https://localhost/', PARAM_URL));
633
        $this->assertSame('https://0.255.1.1/numericip.php', clean_param('https://0.255.1.1/numericip.php', PARAM_URL));
634
        $this->assertSame('ftp://ftp.debian.org/debian/', clean_param('ftp://ftp.debian.org/debian/', PARAM_URL));
635
        $this->assertSame('/just/a/path', clean_param('/just/a/path', PARAM_URL));
636
        // Invalid URLs.
637
        $this->assertSame('', clean_param('funny:thing', PARAM_URL));
638
        $this->assertSame('', clean_param('http://example.ee/sdsf"f', PARAM_URL));
639
        $this->assertSame('', clean_param('javascript://comment%0Aalert(1)', PARAM_URL));
640
        $this->assertSame('', clean_param('rtmp://example.com/livestream', PARAM_URL));
641
        $this->assertSame('', clean_param('rtmp://example.com/live&foo', PARAM_URL));
642
        $this->assertSame('', clean_param('rtmp://example.com/fms&mp4:path/to/file.mp4', PARAM_URL));
643
        $this->assertSame('', clean_param('mailto:support@moodle.org', PARAM_URL));
644
        $this->assertSame('', clean_param('mailto:support@moodle.org?subject=Hello%20Moodle', PARAM_URL));
645
        $this->assertSame('', clean_param('mailto:support@moodle.org?subject=Hello%20Moodle&cc=feedback@moodle.org', PARAM_URL));
646
        $this->assertSame('', clean_param(null, PARAM_URL));
647
    }
648
 
649
    /**
650
     * @covers \core\param
651
     * @covers \clean_param
652
     */
11 efrain 653
    public function test_clean_param_localurl(): void {
1 efrain 654
        global $CFG;
655
 
656
        $this->resetAfterTest();
657
 
658
        // External, invalid.
659
        $this->assertSame('', clean_param('funny:thing', PARAM_LOCALURL));
660
        $this->assertSame('', clean_param('http://google.com/', PARAM_LOCALURL));
661
        $this->assertSame('', clean_param('https://google.com/?test=true', PARAM_LOCALURL));
662
        $this->assertSame('', clean_param('http://some.very.long.and.silly.domain/with/a/path/', PARAM_LOCALURL));
663
 
664
        // Local absolute.
665
        $this->assertSame(clean_param($CFG->wwwroot, PARAM_LOCALURL), $CFG->wwwroot);
666
        $this->assertSame($CFG->wwwroot . '/with/something?else=true',
667
            clean_param($CFG->wwwroot . '/with/something?else=true', PARAM_LOCALURL));
668
 
669
        // Local relative.
670
        $this->assertSame('/just/a/path', clean_param('/just/a/path', PARAM_LOCALURL));
671
        $this->assertSame('course/view.php?id=3', clean_param('course/view.php?id=3', PARAM_LOCALURL));
672
 
673
        // Local absolute HTTPS in a non HTTPS site.
674
        $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot); // Need to simulate non-https site.
675
        $httpsroot = str_replace('http:', 'https:', $CFG->wwwroot);
676
        $this->assertSame('', clean_param($httpsroot, PARAM_LOCALURL));
677
        $this->assertSame('', clean_param($httpsroot . '/with/something?else=true', PARAM_LOCALURL));
678
 
679
        // Local absolute HTTPS in a HTTPS site.
680
        $CFG->wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
681
        $httpsroot = $CFG->wwwroot;
682
        $this->assertSame($httpsroot, clean_param($httpsroot, PARAM_LOCALURL));
683
        $this->assertSame($httpsroot . '/with/something?else=true',
684
            clean_param($httpsroot . '/with/something?else=true', PARAM_LOCALURL));
685
 
686
        // Test open redirects are not possible.
687
        $CFG->wwwroot = 'http://www.example.com';
688
        $this->assertSame('', clean_param('http://www.example.com.evil.net/hack.php', PARAM_LOCALURL));
689
        $CFG->wwwroot = 'https://www.example.com';
690
        $this->assertSame('', clean_param('https://www.example.com.evil.net/hack.php', PARAM_LOCALURL));
691
 
692
        $this->assertSame('', clean_param('', PARAM_LOCALURL));
693
        $this->assertSame('', clean_param(null, PARAM_LOCALURL));
694
    }
695
 
696
    /**
697
     * @covers \core\param
698
     * @covers \clean_param
699
     */
11 efrain 700
    public function test_clean_param_file(): void {
1 efrain 701
        $this->assertSame('correctfile.txt', clean_param('correctfile.txt', PARAM_FILE));
702
        $this->assertSame('badfile.txt', clean_param('b\'a<d`\\/fi:l>e.t"x|t', PARAM_FILE));
703
        $this->assertSame('..parentdirfile.txt', clean_param('../parentdirfile.txt', PARAM_FILE));
704
        $this->assertSame('....grandparentdirfile.txt', clean_param('../../grandparentdirfile.txt', PARAM_FILE));
705
        $this->assertSame('..winparentdirfile.txt', clean_param('..\winparentdirfile.txt', PARAM_FILE));
706
        $this->assertSame('....wingrandparentdir.txt', clean_param('..\..\wingrandparentdir.txt', PARAM_FILE));
707
        $this->assertSame('myfile.a.b.txt', clean_param('myfile.a.b.txt', PARAM_FILE));
708
        $this->assertSame('myfile..a..b.txt', clean_param('myfile..a..b.txt', PARAM_FILE));
709
        $this->assertSame('myfile.a..b...txt', clean_param('myfile.a..b...txt', PARAM_FILE));
710
        $this->assertSame('myfile.a.txt', clean_param('myfile.a.txt', PARAM_FILE));
711
        $this->assertSame('myfile...txt', clean_param('myfile...txt', PARAM_FILE));
712
        $this->assertSame('...jpg', clean_param('...jpg', PARAM_FILE));
713
        $this->assertSame('.a.b.', clean_param('.a.b.', PARAM_FILE));
714
        $this->assertSame('', clean_param('.', PARAM_FILE));
715
        $this->assertSame('', clean_param('..', PARAM_FILE));
716
        $this->assertSame('...', clean_param('...', PARAM_FILE));
717
        $this->assertSame('. . . .', clean_param('. . . .', PARAM_FILE));
718
        $this->assertSame('dontrtrim.me. .. .. . ', clean_param('dontrtrim.me. .. .. . ', PARAM_FILE));
719
        $this->assertSame(' . .dontltrim.me', clean_param(' . .dontltrim.me', PARAM_FILE));
720
        $this->assertSame('here is a tab.txt', clean_param("here is a tab\t.txt", PARAM_FILE));
721
        $this->assertSame('here is a linebreak.txt', clean_param("here is a line\r\nbreak.txt", PARAM_FILE));
722
        $this->assertSame('', clean_param(null, PARAM_FILE));
723
 
724
        // The following behaviours have been maintained although they seem a little odd.
725
        $this->assertSame('funnything', clean_param('funny:thing', PARAM_FILE));
726
        $this->assertSame('.currentdirfile.txt', clean_param('./currentdirfile.txt', PARAM_FILE));
727
        $this->assertSame('ctempwindowsfile.txt', clean_param('c:\temp\windowsfile.txt', PARAM_FILE));
728
        $this->assertSame('homeuserlinuxfile.txt', clean_param('/home/user/linuxfile.txt', PARAM_FILE));
729
        $this->assertSame('~myfile.txt', clean_param('~/myfile.txt', PARAM_FILE));
730
    }
731
 
732
    /**
733
     * @covers \core\param
734
     * @covers \clean_param
735
     */
11 efrain 736
    public function test_clean_param_path(): void {
1 efrain 737
        $this->assertSame('correctfile.txt', clean_param('correctfile.txt', PARAM_PATH));
738
        $this->assertSame('bad/file.txt', clean_param('b\'a<d`\\/fi:l>e.t"x|t', PARAM_PATH));
739
        $this->assertSame('/parentdirfile.txt', clean_param('../parentdirfile.txt', PARAM_PATH));
740
        $this->assertSame('/grandparentdirfile.txt', clean_param('../../grandparentdirfile.txt', PARAM_PATH));
741
        $this->assertSame('/winparentdirfile.txt', clean_param('..\winparentdirfile.txt', PARAM_PATH));
742
        $this->assertSame('/wingrandparentdir.txt', clean_param('..\..\wingrandparentdir.txt', PARAM_PATH));
743
        $this->assertSame('funnything', clean_param('funny:thing', PARAM_PATH));
744
        $this->assertSame('./here', clean_param('./././here', PARAM_PATH));
745
        $this->assertSame('./currentdirfile.txt', clean_param('./currentdirfile.txt', PARAM_PATH));
746
        $this->assertSame('c/temp/windowsfile.txt', clean_param('c:\temp\windowsfile.txt', PARAM_PATH));
747
        $this->assertSame('/home/user/linuxfile.txt', clean_param('/home/user/linuxfile.txt', PARAM_PATH));
748
        $this->assertSame('/home../user ./.linuxfile.txt', clean_param('/home../user ./.linuxfile.txt', PARAM_PATH));
749
        $this->assertSame('~/myfile.txt', clean_param('~/myfile.txt', PARAM_PATH));
750
        $this->assertSame('~/myfile.txt', clean_param('~/../myfile.txt', PARAM_PATH));
751
        $this->assertSame('/..b../.../myfile.txt', clean_param('/..b../.../myfile.txt', PARAM_PATH));
752
        $this->assertSame('..b../.../myfile.txt', clean_param('..b../.../myfile.txt', PARAM_PATH));
753
        $this->assertSame('/super/slashes/', clean_param('/super//slashes///', PARAM_PATH));
754
        $this->assertSame('', clean_param(null, PARAM_PATH));
755
    }
756
 
757
    /**
758
     * @covers \core\param
759
     * @covers \clean_param
760
     */
11 efrain 761
    public function test_clean_param_safepath(): void {
1 efrain 762
        $this->assertSame('folder/file', clean_param('folder/file', PARAM_SAFEPATH));
763
        $this->assertSame('folder//file', clean_param('folder/../file', PARAM_SAFEPATH));
764
        $this->assertSame('', clean_param(null, PARAM_SAFEPATH));
765
    }
766
 
767
    /**
768
     * @covers \core\param
769
     * @covers \clean_param
770
     */
11 efrain 771
    public function test_clean_param_username(): void {
1 efrain 772
        global $CFG;
773
        $currentstatus =  $CFG->extendedusernamechars;
774
 
775
        // Run tests with extended character == false;.
776
        $CFG->extendedusernamechars = false;
777
        $this->assertSame('johndoe123', clean_param('johndoe123', PARAM_USERNAME) );
778
        $this->assertSame('john.doe', clean_param('john.doe', PARAM_USERNAME));
779
        $this->assertSame('john-doe', clean_param('john-doe', PARAM_USERNAME));
780
        $this->assertSame('john-doe', clean_param('john- doe', PARAM_USERNAME));
781
        $this->assertSame('john_doe', clean_param('john_doe', PARAM_USERNAME));
782
        $this->assertSame('john@doe', clean_param('john@doe', PARAM_USERNAME));
783
        $this->assertSame('johndoe', clean_param('john~doe', PARAM_USERNAME));
784
        $this->assertSame('johndoe', clean_param('john´doe', PARAM_USERNAME));
785
        $this->assertSame(clean_param('john# $%&()+_^', PARAM_USERNAME), 'john_');
786
        $this->assertSame(clean_param(' john# $%&()+_^ ', PARAM_USERNAME), 'john_');
787
        $this->assertSame(clean_param('john#$%&() ', PARAM_USERNAME), 'john');
788
        $this->assertSame('johnd', clean_param('JOHNdóé ', PARAM_USERNAME));
789
        $this->assertSame(clean_param('john.,:;-_/|\ñÑ[]A_X-,D {} ~!@#$%^&*()_+ ?><[] ščřžžý ?ýáž?žý??šdoe ', PARAM_USERNAME), 'john.-_a_x-d@_doe');
790
        $this->assertSame('', clean_param(null, PARAM_USERNAME));
791
 
792
        // Test success condition, if extendedusernamechars == ENABLE;.
793
        $CFG->extendedusernamechars = true;
794
        $this->assertSame('john_doe', clean_param('john_doe', PARAM_USERNAME));
795
        $this->assertSame('john@doe', clean_param('john@doe', PARAM_USERNAME));
796
        $this->assertSame(clean_param('john# $%&()+_^', PARAM_USERNAME), 'john# $%&()+_^');
797
        $this->assertSame(clean_param(' john# $%&()+_^ ', PARAM_USERNAME), 'john# $%&()+_^');
798
        $this->assertSame('john~doe', clean_param('john~doe', PARAM_USERNAME));
799
        $this->assertSame('john´doe', clean_param('joHN´doe', PARAM_USERNAME));
800
        $this->assertSame('johndoe', clean_param('johnDOE', PARAM_USERNAME));
801
        $this->assertSame('johndóé', clean_param('johndóé ', PARAM_USERNAME));
802
 
803
        $CFG->extendedusernamechars = $currentstatus;
804
    }
805
 
806
    /**
807
     * @covers \core\param
808
     * @covers \clean_param
809
     */
11 efrain 810
    public function test_clean_param_stringid(): void {
1 efrain 811
        // Test string identifiers validation.
812
        // Valid strings.
813
        $this->assertSame('validstring', clean_param('validstring', PARAM_STRINGID));
814
        $this->assertSame('mod/foobar:valid_capability', clean_param('mod/foobar:valid_capability', PARAM_STRINGID));
815
        $this->assertSame('CZ', clean_param('CZ', PARAM_STRINGID));
816
        $this->assertSame('application/vnd.ms-powerpoint', clean_param('application/vnd.ms-powerpoint', PARAM_STRINGID));
817
        $this->assertSame('grade2', clean_param('grade2', PARAM_STRINGID));
818
        // Invalid strings.
819
        $this->assertSame('', clean_param('trailing ', PARAM_STRINGID));
820
        $this->assertSame('', clean_param('space bar', PARAM_STRINGID));
821
        $this->assertSame('', clean_param('0numeric', PARAM_STRINGID));
822
        $this->assertSame('', clean_param('*', PARAM_STRINGID));
823
        $this->assertSame('', clean_param(' ', PARAM_STRINGID));
824
        $this->assertSame('', clean_param(null, PARAM_STRINGID));
825
    }
826
 
827
    /**
828
     * @covers \core\param
829
     * @covers \clean_param
830
     */
11 efrain 831
    public function test_clean_param_timezone(): void {
1 efrain 832
        // Test timezone validation.
833
        $testvalues = array (
834
            'America/Jamaica'                => 'America/Jamaica',
835
            'America/Argentina/Cordoba'      => 'America/Argentina/Cordoba',
836
            'America/Port-au-Prince'         => 'America/Port-au-Prince',
837
            'America/Argentina/Buenos_Aires' => 'America/Argentina/Buenos_Aires',
838
            'PST8PDT'                        => 'PST8PDT',
839
            'Wrong.Value'                    => '',
840
            'Wrong/.Value'                   => '',
841
            'Wrong(Value)'                   => '',
842
            '0'                              => '0',
843
            '0.0'                            => '0.0',
844
            '0.5'                            => '0.5',
845
            '9.0'                            => '9.0',
846
            '-9.0'                           => '-9.0',
847
            '+9.0'                           => '+9.0',
848
            '9.5'                            => '9.5',
849
            '-9.5'                           => '-9.5',
850
            '+9.5'                           => '+9.5',
851
            '12.0'                           => '12.0',
852
            '-12.0'                          => '-12.0',
853
            '+12.0'                          => '+12.0',
854
            '12.5'                           => '12.5',
855
            '-12.5'                          => '-12.5',
856
            '+12.5'                          => '+12.5',
857
            '13.0'                           => '13.0',
858
            '-13.0'                          => '-13.0',
859
            '+13.0'                          => '+13.0',
860
            '13.5'                           => '',
861
            '+13.5'                          => '',
862
            '-13.5'                          => '',
863
            '0.2'                            => '',
864
            ''                               => '',
865
        );
866
 
867
        foreach ($testvalues as $testvalue => $expectedvalue) {
868
            $actualvalue = clean_param($testvalue, PARAM_TIMEZONE);
869
            $this->assertEquals($expectedvalue, $actualvalue);
870
        }
11 efrain 871
 
872
        // Test for null.
873
        $this->assertEquals('', clean_param(null, PARAM_TIMEZONE));
1 efrain 874
    }
875
 
876
    /**
877
     * @covers \core\param
878
     * @covers \clean_param
879
     */
11 efrain 880
    public function test_clean_param_null_argument(): void {
1 efrain 881
        $this->assertEquals(0, clean_param(null, PARAM_INT));
882
        $this->assertEquals(0, clean_param(null, PARAM_FLOAT));
883
        $this->assertEquals(0, clean_param(null, PARAM_LOCALISEDFLOAT));
884
        $this->assertEquals(false, clean_param(null, PARAM_BOOL));
885
        $this->assertEquals('', clean_param(null, PARAM_NOTAGS));
886
        $this->assertEquals('', clean_param(null, PARAM_SAFEDIR));
887
        $this->assertEquals('', clean_param(null, PARAM_HOST));
888
        $this->assertEquals('', clean_param(null, PARAM_PEM));
889
        $this->assertEquals('', clean_param(null, PARAM_BASE64));
890
        $this->assertEquals('', clean_param(null, PARAM_TAG));
891
        $this->assertEquals('', clean_param(null, PARAM_TAGLIST));
892
        $this->assertEquals('', clean_param(null, PARAM_CAPABILITY));
893
        $this->assertEquals(0, clean_param(null, PARAM_PERMISSION));
894
        $this->assertEquals('', clean_param(null, PARAM_AUTH));
895
        $this->assertEquals('', clean_param(null, PARAM_LANG));
896
        $this->assertEquals('', clean_param(null, PARAM_THEME));
897
        $this->assertEquals('', clean_param(null, PARAM_EMAIL));
898
    }
899
 
11 efrain 900
    public function test_validate_param(): void {
1 efrain 901
        try {
902
            $param = validate_param('11a', PARAM_INT);
903
            $this->fail('invalid_parameter_exception expected');
904
        } catch (\moodle_exception $ex) {
905
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
906
        }
907
 
908
        $param = validate_param('11', PARAM_INT);
909
        $this->assertSame(11, $param);
910
 
911
        try {
912
            $param = validate_param(null, PARAM_INT, false);
913
            $this->fail('invalid_parameter_exception expected');
914
        } catch (\moodle_exception $ex) {
915
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
916
        }
917
 
918
        $param = validate_param(null, PARAM_INT, true);
919
        $this->assertSame(null, $param);
920
 
921
        try {
922
            $param = validate_param(array(), PARAM_INT);
923
            $this->fail('invalid_parameter_exception expected');
924
        } catch (\moodle_exception $ex) {
925
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
926
        }
927
        try {
928
            $param = validate_param(new \stdClass, PARAM_INT);
929
            $this->fail('invalid_parameter_exception expected');
930
        } catch (\moodle_exception $ex) {
931
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
932
        }
933
 
934
        $param = validate_param('1.0', PARAM_FLOAT);
935
        $this->assertSame(1.0, $param);
936
 
937
        // Make sure valid floats do not cause exception.
938
        validate_param(1.0, PARAM_FLOAT);
939
        validate_param(10, PARAM_FLOAT);
940
        validate_param('0', PARAM_FLOAT);
941
        validate_param('119813454.545464564564546564545646556564465465456465465465645645465645645645', PARAM_FLOAT);
942
        validate_param('011.1', PARAM_FLOAT);
943
        validate_param('11', PARAM_FLOAT);
944
        validate_param('+.1', PARAM_FLOAT);
945
        validate_param('-.1', PARAM_FLOAT);
946
        validate_param('1e10', PARAM_FLOAT);
947
        validate_param('.1e+10', PARAM_FLOAT);
948
        validate_param('1E-1', PARAM_FLOAT);
949
 
950
        try {
951
            $param = validate_param('1,2', PARAM_FLOAT);
952
            $this->fail('invalid_parameter_exception expected');
953
        } catch (\moodle_exception $ex) {
954
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
955
        }
956
        try {
957
            $param = validate_param('', PARAM_FLOAT);
958
            $this->fail('invalid_parameter_exception expected');
959
        } catch (\moodle_exception $ex) {
960
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
961
        }
962
        try {
963
            $param = validate_param('.', PARAM_FLOAT);
964
            $this->fail('invalid_parameter_exception expected');
965
        } catch (\moodle_exception $ex) {
966
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
967
        }
968
        try {
969
            $param = validate_param('e10', PARAM_FLOAT);
970
            $this->fail('invalid_parameter_exception expected');
971
        } catch (\moodle_exception $ex) {
972
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
973
        }
974
        try {
975
            $param = validate_param('abc', PARAM_FLOAT);
976
            $this->fail('invalid_parameter_exception expected');
977
        } catch (\moodle_exception $ex) {
978
            $this->assertInstanceOf('invalid_parameter_exception', $ex);
979
        }
980
    }
981
 
11 efrain 982
    public function test_shorten_text_no_tags_already_short_enough(): void {
1 efrain 983
        // ......12345678901234567890123456.
984
        $text = "short text already no tags";
985
        $this->assertSame($text, shorten_text($text));
986
    }
987
 
11 efrain 988
    public function test_shorten_text_with_tags_already_short_enough(): void {
1 efrain 989
        // .........123456...7890....12345678.......901234567.
990
        $text = "<p>short <b>text</b> already</p><p>with tags</p>";
991
        $this->assertSame($text, shorten_text($text));
992
    }
993
 
11 efrain 994
    public function test_shorten_text_no_tags_needs_shortening(): void {
1 efrain 995
        // Default truncation is after 30 chars, but allowing 3 for the final '...'.
996
        // ......12345678901234567890123456789023456789012345678901234.
997
        $text = "long text without any tags blah de blah blah blah what";
998
        $this->assertSame('long text without any tags ...', shorten_text($text));
999
    }
1000
 
11 efrain 1001
    public function test_shorten_text_with_tags_needs_shortening(): void {
1 efrain 1002
        // .......................................123456789012345678901234567890...
1003
        $text = "<div class='frog'><p><blockquote>Long text with tags that will ".
1004
            "be chopped off but <b>should be added back again</b></blockquote></p></div>";
1005
        $this->assertEquals("<div class='frog'><p><blockquote>Long text with " .
1006
            "tags that ...</blockquote></p></div>", shorten_text($text));
1007
    }
1008
 
11 efrain 1009
    public function test_shorten_text_with_tags_and_html_comment(): void {
1 efrain 1010
        $text = "<div class='frog'><p><blockquote><!--[if !IE]><!-->Long text with ".
1011
            "tags that will<!--<![endif]--> ".
1012
            "be chopped off but <b>should be added back again</b></blockquote></p></div>";
1013
        $this->assertEquals("<div class='frog'><p><blockquote><!--[if !IE]><!-->Long text with " .
1014
            "tags that ...<!--<![endif]--></blockquote></p></div>", shorten_text($text));
1015
    }
1016
 
11 efrain 1017
    public function test_shorten_text_with_entities(): void {
1 efrain 1018
        // Remember to allow 3 chars for the final '...'.
1019
        // ......123456789012345678901234567_____890...
1020
        $text = "some text which shouldn't &nbsp; break there";
1021
        $this->assertSame("some text which shouldn't &nbsp; ...", shorten_text($text, 31));
1022
        $this->assertSame("some text which shouldn't &nbsp;...", shorten_text($text, 30));
1023
        $this->assertSame("some text which shouldn't ...", shorten_text($text, 29));
1024
    }
1025
 
11 efrain 1026
    public function test_shorten_text_known_tricky_case(): void {
1 efrain 1027
        // This case caused a bug up to 1.9.5
1028
        // ..........123456789012345678901234567890123456789.....0_____1___2___...
1029
        $text = "<h3>standard 'break-out' sub groups in TGs?</h3>&nbsp;&lt;&lt;There are several";
1030
        $this->assertSame("<h3>standard 'break-out' sub groups in ...</h3>",
1031
            shorten_text($text, 41));
1032
        $this->assertSame("<h3>standard 'break-out' sub groups in TGs?...</h3>",
1033
            shorten_text($text, 42));
1034
        $this->assertSame("<h3>standard 'break-out' sub groups in TGs?</h3>&nbsp;...",
1035
            shorten_text($text, 43));
1036
    }
1037
 
11 efrain 1038
    public function test_shorten_text_no_spaces(): void {
1 efrain 1039
        // ..........123456789.
1040
        $text = "<h1>123456789</h1>"; // A string with no convenient breaks.
1041
        $this->assertSame("<h1>12345...</h1>", shorten_text($text, 8));
1042
    }
1043
 
11 efrain 1044
    public function test_shorten_text_utf8_european(): void {
1 efrain 1045
        // Text without tags.
1046
        // ......123456789012345678901234567.
1047
        $text = "Žluťoučký koníček přeskočil";
1048
        $this->assertSame($text, shorten_text($text)); // 30 chars by default.
1049
        $this->assertSame("Žluťoučký koníče...", shorten_text($text, 19, true));
1050
        $this->assertSame("Žluťoučký ...", shorten_text($text, 19, false));
1051
        // And try it with 2-less (that are, in bytes, the middle of a sequence).
1052
        $this->assertSame("Žluťoučký koní...", shorten_text($text, 17, true));
1053
        $this->assertSame("Žluťoučký ...", shorten_text($text, 17, false));
1054
 
1055
        // .........123456789012345678...901234567....89012345.
1056
        $text = "<p>Žluťoučký koníček <b>přeskočil</b> potůček</p>";
1057
        $this->assertSame($text, shorten_text($text, 60));
1058
        $this->assertSame("<p>Žluťoučký koníček ...</p>", shorten_text($text, 21));
1059
        $this->assertSame("<p>Žluťoučký koníče...</p>", shorten_text($text, 19, true));
1060
        $this->assertSame("<p>Žluťoučký ...</p>", shorten_text($text, 19, false));
1061
        // And try it with 2 fewer (that are, in bytes, the middle of a sequence).
1062
        $this->assertSame("<p>Žluťoučký koní...</p>", shorten_text($text, 17, true));
1063
        $this->assertSame("<p>Žluťoučký ...</p>", shorten_text($text, 17, false));
1064
        // And try over one tag (start/end), it does proper text len.
1065
        $this->assertSame("<p>Žluťoučký koníček <b>př...</b></p>", shorten_text($text, 23, true));
1066
        $this->assertSame("<p>Žluťoučký koníček <b>přeskočil</b> pot...</p>", shorten_text($text, 34, true));
1067
        // And in the middle of one tag.
1068
        $this->assertSame("<p>Žluťoučký koníček <b>přeskočil...</b></p>", shorten_text($text, 30, true));
1069
    }
1070
 
11 efrain 1071
    public function test_shorten_text_utf8_oriental(): void {
1 efrain 1072
        // Japanese
1073
        // text without tags
1074
        // ......123456789012345678901234.
1075
        $text = '言語設定言語設定abcdefghijkl';
1076
        $this->assertSame($text, shorten_text($text)); // 30 chars by default.
1077
        $this->assertSame("言語設定言語...", shorten_text($text, 9, true));
1078
        $this->assertSame("言語設定言語...", shorten_text($text, 9, false));
1079
        $this->assertSame("言語設定言語設定ab...", shorten_text($text, 13, true));
1080
        $this->assertSame("言語設定言語設定...", shorten_text($text, 13, false));
1081
 
1082
        // Chinese
1083
        // text without tags
1084
        // ......123456789012345678901234.
1085
        $text = '简体中文简体中文abcdefghijkl';
1086
        $this->assertSame($text, shorten_text($text)); // 30 chars by default.
1087
        $this->assertSame("简体中文简体...", shorten_text($text, 9, true));
1088
        $this->assertSame("简体中文简体...", shorten_text($text, 9, false));
1089
        $this->assertSame("简体中文简体中文ab...", shorten_text($text, 13, true));
1090
        $this->assertSame("简体中文简体中文...", shorten_text($text, 13, false));
1091
    }
1092
 
11 efrain 1093
    public function test_shorten_text_multilang(): void {
1 efrain 1094
        // This is not necessaryily specific to multilang. The issue is really
1095
        // tags with attributes, where before we were generating invalid HTML
1096
        // output like shorten_text('<span id="x" class="y">A</span> B', 1)
1097
        // returning '<span id="x" ...</span>'. It is just that multilang
1098
        // requires the sort of HTML that is quite likely to trigger this.
1099
        // ........................................1...
1100
        $text = '<span lang="en" class="multilang">A</span>' .
1101
                '<span lang="fr" class="multilang">B</span>';
1102
        $this->assertSame('<span lang="en" class="multilang">...</span>',
1103
                shorten_text($text, 1));
1104
    }
1105
 
1106
    /**
1107
     * Provider for long filenames and its expected result, with and without hash.
1108
     *
1109
     * @return array of ($filename, $length, $expected, $includehash)
1110
     */
1441 ariadna 1111
    public static function shorten_filename_provider(): array {
1 efrain 1112
        $filename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium totam rem';
1113
        $shortfilename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque';
1114
 
1115
        return [
1116
            'More than 100 characters' => [
1117
                $filename,
1118
                null,
1119
                'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium tot',
1120
                false,
1121
            ],
1122
            'More than 100 characters with hash' => [
1123
                $filename,
1124
                null,
1125
                'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque l - 3bec1da8b8',
1126
                true,
1127
            ],
1128
            'More than 100 characters with extension' => [
1129
                "{$filename}.zip",
1130
                null,
1131
                'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium tot.zip',
1132
                false,
1133
            ],
1134
            'More than 100 characters with extension and hash' => [
1135
                "{$filename}.zip",
1136
                null,
1137
                'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque l - 3bec1da8b8.zip',
1138
                true,
1139
            ],
1140
            'Limit filename to 50 chars' => [
1141
                $filename,
1142
                50,
1143
                'sed ut perspiciatis unde omnis iste natus error si',
1144
                false,
1145
            ],
1146
            'Limit filename to 50 chars with hash' => [
1147
                $filename,
1148
                50,
1149
                'sed ut perspiciatis unde omnis iste n - 3bec1da8b8',
1150
                true,
1151
            ],
1152
            'Limit filename to 50 chars with extension' => [
1153
                "{$filename}.zip",
1154
                50,
1155
                'sed ut perspiciatis unde omnis iste natus error si.zip',
1156
                false,
1157
            ],
1158
            'Limit filename to 50 chars with extension and hash' => [
1159
                "{$filename}.zip",
1160
                50,
1161
                'sed ut perspiciatis unde omnis iste n - 3bec1da8b8.zip',
1162
                true,
1163
            ],
1164
            'Test filename that contains less than 100 characters' => [
1165
                $shortfilename,
1166
                null,
1167
                $shortfilename,
1168
                false,
1169
            ],
1170
            'Test filename that contains less than 100 characters and hash' => [
1171
                $shortfilename,
1172
                null,
1173
                $shortfilename,
1174
                true,
1175
            ],
1176
            'Test filename that contains less than 100 characters with extension' => [
1177
                "{$shortfilename}.zip",
1178
                null,
1179
                "{$shortfilename}.zip",
1180
                false,
1181
            ],
1182
            'Test filename that contains less than 100 characters with extension and hash' => [
1183
                "{$shortfilename}.zip",
1184
                null,
1185
                "{$shortfilename}.zip",
1186
                true,
1187
            ],
1188
        ];
1189
    }
1190
 
1191
    /**
1192
     * Test the {@link shorten_filename()} method.
1193
     *
1194
     * @dataProvider shorten_filename_provider
1195
     *
1196
     * @param string $filename
1197
     * @param int $length
1198
     * @param string $expected
1199
     * @param boolean $includehash
1200
     */
11 efrain 1201
    public function test_shorten_filename($filename, $length, $expected, $includehash): void {
1 efrain 1202
        if (null === $length) {
1203
            $length = MAX_FILENAME_SIZE;
1204
        }
1205
 
1206
        $this->assertSame($expected, shorten_filename($filename, $length, $includehash));
1207
    }
1208
 
1209
    /**
1210
     * Provider for long filenames and its expected result, with and without hash.
1211
     *
1212
     * @return array of ($filename, $length, $expected, $includehash)
1213
     */
1441 ariadna 1214
    public static function shorten_filenames_provider(): array {
1 efrain 1215
        $shortfilename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque';
1216
        $longfilename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium totam rem';
1217
        $extfilename = $longfilename.'.zip';
1218
        $expected = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium tot';
1219
        $expectedwithhash = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque l - 3bec1da8b8';
1220
        $expectedext = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium tot.zip';
1221
        $expectedextwithhash = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque l - 3bec1da8b8.zip';
1222
        $expected50 = 'sed ut perspiciatis unde omnis iste natus error si';
1223
        $expected50withhash = 'sed ut perspiciatis unde omnis iste n - 3bec1da8b8';
1224
        $expected50ext = 'sed ut perspiciatis unde omnis iste natus error si.zip';
1225
        $expected50extwithhash = 'sed ut perspiciatis unde omnis iste n - 3bec1da8b8.zip';
1226
        $expected50short = 'sed ut perspiciatis unde omnis iste n - 5fb6543490';
1227
 
1228
        return [
1229
            'Empty array without hash' => [
1230
                [],
1231
                null,
1232
                [],
1233
                false,
1234
            ],
1235
            'Empty array with hash' => [
1236
                [],
1237
                null,
1238
                [],
1239
                true,
1240
            ],
1241
            'Array with less than 100 characters' => [
1242
                [$shortfilename, $shortfilename, $shortfilename],
1243
                null,
1244
                [$shortfilename, $shortfilename, $shortfilename],
1245
                false,
1246
            ],
1247
            'Array with more than 100 characters without hash' => [
1248
                [$longfilename, $longfilename, $longfilename],
1249
                null,
1250
                [$expected, $expected, $expected],
1251
                false,
1252
            ],
1253
            'Array with more than 100 characters with hash' => [
1254
                [$longfilename, $longfilename, $longfilename],
1255
                null,
1256
                [$expectedwithhash, $expectedwithhash, $expectedwithhash],
1257
                true,
1258
            ],
1259
            'Array with more than 100 characters with extension' => [
1260
                [$extfilename, $extfilename, $extfilename],
1261
                null,
1262
                [$expectedext, $expectedext, $expectedext],
1263
                false,
1264
            ],
1265
            'Array with more than 100 characters with extension and hash' => [
1266
                [$extfilename, $extfilename, $extfilename],
1267
                null,
1268
                [$expectedextwithhash, $expectedextwithhash, $expectedextwithhash],
1269
                true,
1270
            ],
1271
            'Array with more than 100 characters mix (short, long, with extension) without hash' => [
1272
                [$shortfilename, $longfilename, $extfilename],
1273
                null,
1274
                [$shortfilename, $expected, $expectedext],
1275
                false,
1276
            ],
1277
            'Array with more than 100 characters mix (short, long, with extension) with hash' => [
1278
                [$shortfilename, $longfilename, $extfilename],
1279
                null,
1280
                [$shortfilename, $expectedwithhash, $expectedextwithhash],
1281
                true,
1282
            ],
1283
            'Array with less than 50 characters without hash' => [
1284
                [$longfilename, $longfilename, $longfilename],
1285
                50,
1286
                [$expected50, $expected50, $expected50],
1287
                false,
1288
            ],
1289
            'Array with less than 50 characters with hash' => [
1290
                [$longfilename, $longfilename, $longfilename],
1291
                50,
1292
                [$expected50withhash, $expected50withhash, $expected50withhash],
1293
                true,
1294
            ],
1295
            'Array with less than 50 characters with extension' => [
1296
                [$extfilename, $extfilename, $extfilename],
1297
                50,
1298
                [$expected50ext, $expected50ext, $expected50ext],
1299
                false,
1300
            ],
1301
            'Array with less than 50 characters with extension and hash' => [
1302
                [$extfilename, $extfilename, $extfilename],
1303
                50,
1304
                [$expected50extwithhash, $expected50extwithhash, $expected50extwithhash],
1305
                true,
1306
            ],
1307
            'Array with less than 50 characters mix (short, long, with extension) without hash' => [
1308
                [$shortfilename, $longfilename, $extfilename],
1309
                50,
1310
                [$expected50, $expected50, $expected50ext],
1311
                false,
1312
            ],
1313
            'Array with less than 50 characters mix (short, long, with extension) with hash' => [
1314
                [$shortfilename, $longfilename, $extfilename],
1315
                50,
1316
                [$expected50short, $expected50withhash, $expected50extwithhash],
1317
                true,
1318
            ],
1319
        ];
1320
    }
1321
 
1322
    /**
1323
     * Test the {@link shorten_filenames()} method.
1324
     *
1325
     * @dataProvider shorten_filenames_provider
1326
     *
1327
     * @param array $filenames
1328
     * @param int $length
1329
     * @param string $expected
1330
     * @param boolean $includehash
1331
     */
11 efrain 1332
    public function test_shorten_filenames($filenames, $length, $expected, $includehash): void {
1 efrain 1333
        if (null === $length) {
1334
            $length = MAX_FILENAME_SIZE;
1335
        }
1336
 
1337
        $this->assertSame($expected, shorten_filenames($filenames, $length, $includehash));
1338
    }
1339
 
11 efrain 1340
    public function test_usergetdate(): void {
1 efrain 1341
        global $USER, $CFG, $DB;
1342
        $this->resetAfterTest();
1343
 
1344
        $this->setAdminUser();
1345
 
1346
        $USER->timezone = 2;// Set the timezone to a known state.
1347
 
1348
        $ts = 1261540267; // The time this function was created.
1349
 
1350
        $arr = usergetdate($ts, 1); // Specify the timezone as an argument.
1351
        $arr = array_values($arr);
1352
 
1353
        list($seconds, $minutes, $hours, $mday, $wday, $mon, $year, $yday, $weekday, $month) = $arr;
1354
        $this->assertSame(7, $seconds);
1355
        $this->assertSame(51, $minutes);
1356
        $this->assertSame(4, $hours);
1357
        $this->assertSame(23, $mday);
1358
        $this->assertSame(3, $wday);
1359
        $this->assertSame(12, $mon);
1360
        $this->assertSame(2009, $year);
1361
        $this->assertSame(356, $yday);
1362
        $this->assertSame('Wednesday', $weekday);
1363
        $this->assertSame('December', $month);
1364
        $arr = usergetdate($ts); // Gets the timezone from the $USER object.
1365
        $arr = array_values($arr);
1366
 
1367
        list($seconds, $minutes, $hours, $mday, $wday, $mon, $year, $yday, $weekday, $month) = $arr;
1368
        $this->assertSame(7, $seconds);
1369
        $this->assertSame(51, $minutes);
1370
        $this->assertSame(5, $hours);
1371
        $this->assertSame(23, $mday);
1372
        $this->assertSame(3, $wday);
1373
        $this->assertSame(12, $mon);
1374
        $this->assertSame(2009, $year);
1375
        $this->assertSame(356, $yday);
1376
        $this->assertSame('Wednesday', $weekday);
1377
        $this->assertSame('December', $month);
1378
 
1379
        // Edge cases - 0 and null - they all mean 1st Jan 1970. Null shows debugging message.
1380
        $this->assertSame(1970, usergetdate(0)['year']);
1381
        $this->assertDebuggingNotCalled();
1382
        $this->assertSame(1970, usergetdate(null)['year']);
1383
        $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
1384
    }
1385
 
11 efrain 1386
    public function test_mark_user_preferences_changed(): void {
1 efrain 1387
        $this->resetAfterTest();
1388
        $otheruser = $this->getDataGenerator()->create_user();
1389
        $otheruserid = $otheruser->id;
1390
 
1391
        set_cache_flag('userpreferenceschanged', $otheruserid, null);
1392
        mark_user_preferences_changed($otheruserid);
1393
 
1394
        $this->assertEquals(get_cache_flag('userpreferenceschanged', $otheruserid, time()-10), 1);
1395
        set_cache_flag('userpreferenceschanged', $otheruserid, null);
1396
    }
1397
 
11 efrain 1398
    public function test_check_user_preferences_loaded(): void {
1 efrain 1399
        global $DB;
1400
        $this->resetAfterTest();
1401
 
1402
        $otheruser = $this->getDataGenerator()->create_user();
1403
        $otheruserid = $otheruser->id;
1404
 
1405
        $DB->delete_records('user_preferences', array('userid'=>$otheruserid));
1406
        set_cache_flag('userpreferenceschanged', $otheruserid, null);
1407
 
1408
        $user = new \stdClass();
1409
        $user->id = $otheruserid;
1410
 
1411
        // Load.
1412
        check_user_preferences_loaded($user);
1413
        $this->assertTrue(isset($user->preference));
1414
        $this->assertTrue(is_array($user->preference));
1415
        $this->assertArrayHasKey('_lastloaded', $user->preference);
1416
        $this->assertCount(1, $user->preference);
1417
 
1418
        // Add preference via direct call.
1419
        $DB->insert_record('user_preferences', array('name'=>'xxx', 'value'=>'yyy', 'userid'=>$user->id));
1420
 
1421
        // No cache reload yet.
1422
        check_user_preferences_loaded($user);
1423
        $this->assertCount(1, $user->preference);
1424
 
1425
        // Forced reloading of cache.
1426
        unset($user->preference);
1427
        check_user_preferences_loaded($user);
1428
        $this->assertCount(2, $user->preference);
1429
        $this->assertSame('yyy', $user->preference['xxx']);
1430
 
1431
        // Add preference via direct call.
1432
        $DB->insert_record('user_preferences', array('name'=>'aaa', 'value'=>'bbb', 'userid'=>$user->id));
1433
 
1434
        // Test timeouts and modifications from different session.
1435
        set_cache_flag('userpreferenceschanged', $user->id, 1, time() + 1000);
1436
        $user->preference['_lastloaded'] = $user->preference['_lastloaded'] - 20;
1437
        check_user_preferences_loaded($user);
1438
        $this->assertCount(2, $user->preference);
1439
        check_user_preferences_loaded($user, 10);
1440
        $this->assertCount(3, $user->preference);
1441
        $this->assertSame('bbb', $user->preference['aaa']);
1442
        set_cache_flag('userpreferenceschanged', $user->id, null);
1443
    }
1444
 
11 efrain 1445
    public function test_set_user_preference(): void {
1 efrain 1446
        global $DB, $USER;
1447
        $this->resetAfterTest();
1448
 
1449
        $this->setAdminUser();
1450
 
1451
        $otheruser = $this->getDataGenerator()->create_user();
1452
        $otheruserid = $otheruser->id;
1453
 
1454
        $DB->delete_records('user_preferences', array('userid'=>$otheruserid));
1455
        set_cache_flag('userpreferenceschanged', $otheruserid, null);
1456
 
1457
        $user = new \stdClass();
1458
        $user->id = $otheruserid;
1459
 
1460
        set_user_preference('aaa', 'bbb', $otheruserid);
1461
        $this->assertSame('bbb', $DB->get_field('user_preferences', 'value', array('userid'=>$otheruserid, 'name'=>'aaa')));
1462
        $this->assertSame('bbb', get_user_preferences('aaa', null, $otheruserid));
1463
 
1464
        set_user_preference('xxx', 'yyy', $user);
1465
        $this->assertSame('yyy', $DB->get_field('user_preferences', 'value', array('userid'=>$otheruserid, 'name'=>'xxx')));
1466
        $this->assertSame('yyy', get_user_preferences('xxx', null, $otheruserid));
1467
        $this->assertTrue(is_array($user->preference));
1468
        $this->assertSame('bbb', $user->preference['aaa']);
1469
        $this->assertSame('yyy', $user->preference['xxx']);
1470
 
1471
        set_user_preference('xxx', null, $user);
1472
        $this->assertFalse($DB->get_field('user_preferences', 'value', array('userid'=>$otheruserid, 'name'=>'xxx')));
1473
        $this->assertNull(get_user_preferences('xxx', null, $otheruserid));
1474
 
1475
        set_user_preference('ooo', true, $user);
1476
        $prefs = get_user_preferences(null, null, $otheruserid);
1477
        $this->assertSame($user->preference['aaa'], $prefs['aaa']);
1478
        $this->assertSame($user->preference['ooo'], $prefs['ooo']);
1479
        $this->assertSame('1', $prefs['ooo']);
1480
 
1481
        set_user_preference('null', 0, $user);
1482
        $this->assertSame('0', get_user_preferences('null', null, $otheruserid));
1483
 
1484
        $this->assertSame('lala', get_user_preferences('undefined', 'lala', $otheruserid));
1485
 
1486
        $DB->delete_records('user_preferences', array('userid'=>$otheruserid));
1487
        set_cache_flag('userpreferenceschanged', $otheruserid, null);
1488
 
1489
        // Test $USER default.
1490
        set_user_preference('_test_user_preferences_pref', 'ok');
1491
        $this->assertSame('ok', $USER->preference['_test_user_preferences_pref']);
1492
        unset_user_preference('_test_user_preferences_pref');
1493
        $this->assertTrue(!isset($USER->preference['_test_user_preferences_pref']));
1494
 
1495
        // Test 1333 char values (no need for unicode, there are already tests for that in DB tests).
1496
        $longvalue = str_repeat('a', 1333);
1497
        set_user_preference('_test_long_user_preference', $longvalue);
1498
        $this->assertEquals($longvalue, get_user_preferences('_test_long_user_preference'));
1499
        $this->assertEquals($longvalue,
1500
            $DB->get_field('user_preferences', 'value', array('userid' => $USER->id, 'name' => '_test_long_user_preference')));
1501
 
1441 ariadna 1502
        // Larger preference values are allowed as of MDL-46739.
1503
        $longervalue = str_repeat('a', 1334);
1504
        set_user_preference('_test_long_user_preference', $longervalue);
1505
        $this->assertEquals($longervalue, get_user_preferences('_test_long_user_preference'));
1506
        $this->assertEquals(
1507
            $longervalue,
1508
            $DB->get_field('user_preferences', 'value', ['userid' => $USER->id, 'name' => '_test_long_user_preference'])
1509
        );
1 efrain 1510
 
1511
        // Test invalid params.
1512
        try {
1513
            set_user_preference('_test_user_preferences_pref', array());
1514
            $this->fail('Exception expected - array not valid preference value');
1515
        } catch (\moodle_exception $ex) {
1516
            $this->assertInstanceOf('coding_exception', $ex);
1517
        }
1518
        try {
1519
            set_user_preference('_test_user_preferences_pref', new \stdClass);
1520
            $this->fail('Exception expected - class not valid preference value');
1521
        } catch (\moodle_exception $ex) {
1522
            $this->assertInstanceOf('coding_exception', $ex);
1523
        }
1524
        try {
1525
            set_user_preference('_test_user_preferences_pref', 1, array('xx' => 1));
1526
            $this->fail('Exception expected - user instance expected');
1527
        } catch (\moodle_exception $ex) {
1528
            $this->assertInstanceOf('coding_exception', $ex);
1529
        }
1530
        try {
1531
            set_user_preference('_test_user_preferences_pref', 1, 'abc');
1532
            $this->fail('Exception expected - user instance expected');
1533
        } catch (\moodle_exception $ex) {
1534
            $this->assertInstanceOf('coding_exception', $ex);
1535
        }
1536
        try {
1537
            set_user_preference('', 1);
1538
            $this->fail('Exception expected - invalid name accepted');
1539
        } catch (\moodle_exception $ex) {
1540
            $this->assertInstanceOf('coding_exception', $ex);
1541
        }
1542
        try {
1543
            set_user_preference('1', 1);
1544
            $this->fail('Exception expected - invalid name accepted');
1545
        } catch (\moodle_exception $ex) {
1546
            $this->assertInstanceOf('coding_exception', $ex);
1547
        }
1548
    }
1549
 
11 efrain 1550
    public function test_set_user_preference_for_current_user(): void {
1 efrain 1551
        global $USER;
1552
        $this->resetAfterTest();
1553
        $this->setAdminUser();
1554
 
1555
        set_user_preference('test_pref', 2);
1556
        set_user_preference('test_pref', 1, $USER->id);
1557
        $this->assertEquals(1, get_user_preferences('test_pref'));
1558
    }
1559
 
11 efrain 1560
    public function test_unset_user_preference_for_current_user(): void {
1 efrain 1561
        global $USER;
1562
        $this->resetAfterTest();
1563
        $this->setAdminUser();
1564
 
1565
        set_user_preference('test_pref', 1);
1566
        unset_user_preference('test_pref', $USER->id);
1567
        $this->assertNull(get_user_preferences('test_pref'));
1568
    }
1569
 
1570
    /**
1571
     * Test some critical TZ/DST.
1572
     *
1573
     * This method tests some special TZ/DST combinations that were fixed
1574
     * by MDL-38999. The tests are done by comparing the results of the
1575
     * output using Moodle TZ/DST support and PHP native one.
1576
     *
1577
     * Note: If you don't trust PHP TZ/DST support, can verify the
1578
     * harcoded expectations below with:
1579
     * http://www.tools4noobs.com/online_tools/unix_timestamp_to_datetime/
1580
     */
11 efrain 1581
    public function test_some_moodle_special_dst(): void {
1 efrain 1582
        $stamp = 1365386400; // 2013/04/08 02:00:00 GMT/UTC.
1583
 
1584
        // In Europe/Tallinn it was 2013/04/08 05:00:00.
1585
        $expectation = '2013/04/08 05:00:00';
1586
        $phpdt = \DateTime::createFromFormat('U', $stamp, new \DateTimeZone('UTC'));
1587
        $phpdt->setTimezone(new \DateTimeZone('Europe/Tallinn'));
1588
        $phpres = $phpdt->format('Y/m/d H:i:s'); // PHP result.
1589
        $moodleres = userdate($stamp, '%Y/%m/%d %H:%M:%S', 'Europe/Tallinn', false); // Moodle result.
1590
        $this->assertSame($expectation, $phpres);
1591
        $this->assertSame($expectation, $moodleres);
1592
 
1593
        // In St. Johns it was 2013/04/07 23:30:00.
1594
        $expectation = '2013/04/07 23:30:00';
1595
        $phpdt = \DateTime::createFromFormat('U', $stamp, new \DateTimeZone('UTC'));
1596
        $phpdt->setTimezone(new \DateTimeZone('America/St_Johns'));
1597
        $phpres = $phpdt->format('Y/m/d H:i:s'); // PHP result.
1598
        $moodleres = userdate($stamp, '%Y/%m/%d %H:%M:%S', 'America/St_Johns', false); // Moodle result.
1599
        $this->assertSame($expectation, $phpres);
1600
        $this->assertSame($expectation, $moodleres);
1601
 
1602
        $stamp = 1383876000; // 2013/11/08 02:00:00 GMT/UTC.
1603
 
1604
        // In Europe/Tallinn it was 2013/11/08 04:00:00.
1605
        $expectation = '2013/11/08 04:00:00';
1606
        $phpdt = \DateTime::createFromFormat('U', $stamp, new \DateTimeZone('UTC'));
1607
        $phpdt->setTimezone(new \DateTimeZone('Europe/Tallinn'));
1608
        $phpres = $phpdt->format('Y/m/d H:i:s'); // PHP result.
1609
        $moodleres = userdate($stamp, '%Y/%m/%d %H:%M:%S', 'Europe/Tallinn', false); // Moodle result.
1610
        $this->assertSame($expectation, $phpres);
1611
        $this->assertSame($expectation, $moodleres);
1612
 
1613
        // In St. Johns it was 2013/11/07 22:30:00.
1614
        $expectation = '2013/11/07 22:30:00';
1615
        $phpdt = \DateTime::createFromFormat('U', $stamp, new \DateTimeZone('UTC'));
1616
        $phpdt->setTimezone(new \DateTimeZone('America/St_Johns'));
1617
        $phpres = $phpdt->format('Y/m/d H:i:s'); // PHP result.
1618
        $moodleres = userdate($stamp, '%Y/%m/%d %H:%M:%S', 'America/St_Johns', false); // Moodle result.
1619
        $this->assertSame($expectation, $phpres);
1620
        $this->assertSame($expectation, $moodleres);
1621
    }
1622
 
11 efrain 1623
    public function test_userdate(): void {
1 efrain 1624
        global $USER, $CFG, $DB;
1625
        $this->resetAfterTest();
1626
 
1627
        $this->setAdminUser();
1628
 
1629
        $testvalues = array(
1630
            array(
1631
                'time' => '1309514400',
1632
                'usertimezone' => 'America/Moncton',
1633
                'timezone' => '0.0', // No dst offset.
1634
                'expectedoutput' => 'Friday, 1 July 2011, 10:00 AM',
1635
                'expectedoutputhtml' => '<time datetime="2011-07-01T07:00:00-03:00">Friday, 1 July 2011, 10:00 AM</time>'
1636
            ),
1637
            array(
1638
                'time' => '1309514400',
1639
                'usertimezone' => 'America/Moncton',
1640
                'timezone' => '99', // Dst offset and timezone offset.
1641
                'expectedoutput' => 'Friday, 1 July 2011, 7:00 AM',
1642
                'expectedoutputhtml' => '<time datetime="2011-07-01T07:00:00-03:00">Friday, 1 July 2011, 7:00 AM</time>'
1643
            ),
1644
            array(
1645
                'time' => '1309514400',
1646
                'usertimezone' => 'America/Moncton',
1647
                'timezone' => 'America/Moncton', // Dst offset and timezone offset.
1648
                'expectedoutput' => 'Friday, 1 July 2011, 7:00 AM',
1649
                'expectedoutputhtml' => '<time datetime="2011-07-01t07:00:00-03:00">Friday, 1 July 2011, 7:00 AM</time>'
1650
            ),
1651
            array(
1652
                'time' => '1293876000 ',
1653
                'usertimezone' => 'America/Moncton',
1654
                'timezone' => '0.0', // No dst offset.
1655
                'expectedoutput' => 'Saturday, 1 January 2011, 10:00 AM',
1656
                'expectedoutputhtml' => '<time datetime="2011-01-01T06:00:00-04:00">Saturday, 1 January 2011, 10:00 AM</time>'
1657
            ),
1658
            array(
1659
                'time' => '1293876000 ',
1660
                'usertimezone' => 'America/Moncton',
1661
                'timezone' => '99', // No dst offset in jan, so just timezone offset.
1662
                'expectedoutput' => 'Saturday, 1 January 2011, 6:00 AM',
1663
                'expectedoutputhtml' => '<time datetime="2011-01-01T06:00:00-04:00">Saturday, 1 January 2011, 6:00 AM</time>'
1664
            ),
1665
            array(
1666
                'time' => '1293876000 ',
1667
                'usertimezone' => 'America/Moncton',
1668
                'timezone' => 'America/Moncton', // No dst offset in jan.
1669
                'expectedoutput' => 'Saturday, 1 January 2011, 6:00 AM',
1670
                'expectedoutputhtml' => '<time datetime="2011-01-01T06:00:00-04:00">Saturday, 1 January 2011, 6:00 AM</time>'
1671
            ),
1672
            array(
1673
                'time' => '1293876000 ',
1674
                'usertimezone' => '2',
1675
                'timezone' => '99', // Take user timezone.
1676
                'expectedoutput' => 'Saturday, 1 January 2011, 12:00 PM',
1677
                'expectedoutputhtml' => '<time datetime="2011-01-01T12:00:00+02:00">Saturday, 1 January 2011, 12:00 PM</time>'
1678
            ),
1679
            array(
1680
                'time' => '1293876000 ',
1681
                'usertimezone' => '-2',
1682
                'timezone' => '99', // Take user timezone.
1683
                'expectedoutput' => 'Saturday, 1 January 2011, 8:00 AM',
1684
                'expectedoutputhtml' => '<time datetime="2011-01-01T08:00:00-02:00">Saturday, 1 January 2011, 8:00 AM</time>'
1685
            ),
1686
            array(
1687
                'time' => '1293876000 ',
1688
                'usertimezone' => '-10',
1689
                'timezone' => '2', // Take this timezone.
1690
                'expectedoutput' => 'Saturday, 1 January 2011, 12:00 PM',
1691
                'expectedoutputhtml' => '<time datetime="2011-01-01T00:00:00-10:00">Saturday, 1 January 2011, 12:00 PM</time>'
1692
            ),
1693
            array(
1694
                'time' => '1293876000 ',
1695
                'usertimezone' => '-10',
1696
                'timezone' => '-2', // Take this timezone.
1697
                'expectedoutput' => 'Saturday, 1 January 2011, 8:00 AM',
1698
                'expectedoutputhtml' => '<time datetime="2011-01-01T00:00:00-10:00">Saturday, 1 January 2011, 8:00 AM</time>'
1699
            ),
1700
            array(
1701
                'time' => '1293876000 ',
1702
                'usertimezone' => '-10',
1703
                'timezone' => 'random/time', // This should show server time.
1704
                'expectedoutput' => 'Saturday, 1 January 2011, 6:00 PM',
1705
                'expectedoutputhtml' => '<time datetime="2011-01-01T00:00:00-10:00">Saturday, 1 January 2011, 6:00 PM</time>'
1706
            ),
1707
            array(
1708
                'time' => '1293876000 ',
1709
                'usertimezone' => '20', // Fallback to server time zone.
1710
                'timezone' => '99',     // This should show user time.
1711
                'expectedoutput' => 'Saturday, 1 January 2011, 6:00 PM',
1712
                'expectedoutputhtml' => '<time datetime="2011-01-01T18:00:00+08:00">Saturday, 1 January 2011, 6:00 PM</time>'
1713
            ),
1714
        );
1715
 
1716
        // Set default timezone to Australia/Perth, else time calculated
1717
        // will not match expected values.
1718
        $this->setTimezone(99, 'Australia/Perth');
1719
 
1720
        foreach ($testvalues as $vals) {
1721
            $USER->timezone = $vals['usertimezone'];
1722
            $actualoutput = userdate($vals['time'], '%A, %d %B %Y, %I:%M %p', $vals['timezone']);
1723
            $actualoutputhtml = userdate_htmltime($vals['time'], '%A, %d %B %Y, %I:%M %p', $vals['timezone']);
1724
 
1725
            // On different systems case of AM PM changes so compare case insensitive.
1726
            $vals['expectedoutput'] = \core_text::strtolower($vals['expectedoutput']);
1727
            $vals['expectedoutputhtml'] = \core_text::strtolower($vals['expectedoutputhtml']);
1728
            $actualoutput = \core_text::strtolower($actualoutput);
1729
            $actualoutputhtml = \core_text::strtolower($actualoutputhtml);
1730
 
1731
            $this->assertSame($vals['expectedoutput'], $actualoutput,
1732
                "Expected: {$vals['expectedoutput']} => Actual: {$actualoutput} \ndata: " . var_export($vals, true));
1733
            $this->assertSame($vals['expectedoutputhtml'], $actualoutputhtml,
1734
                "Expected: {$vals['expectedoutputhtml']} => Actual: {$actualoutputhtml} \ndata: " . var_export($vals, true));
1735
        }
1736
    }
1737
 
1738
    /**
1739
     * Make sure the DST changes happen at the right time in Moodle.
1740
     */
11 efrain 1741
    public function test_dst_changes(): void {
1 efrain 1742
        // DST switching in Prague.
1743
        // From 2AM to 3AM in 1989.
1744
        $date = new \DateTime('1989-03-26T01:59:00+01:00');
1745
        $this->assertSame('Sunday, 26 March 1989, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1746
        $date = new \DateTime('1989-03-26T02:01:00+01:00');
1747
        $this->assertSame('Sunday, 26 March 1989, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1748
        // From 3AM to 2AM in 1989 - not the same as the west Europe.
1749
        $date = new \DateTime('1989-09-24T01:59:00+01:00');
1750
        $this->assertSame('Sunday, 24 September 1989, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1751
        $date = new \DateTime('1989-09-24T02:01:00+01:00');
1752
        $this->assertSame('Sunday, 24 September 1989, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1753
        // From 2AM to 3AM in 2014.
1754
        $date = new \DateTime('2014-03-30T01:59:00+01:00');
1755
        $this->assertSame('Sunday, 30 March 2014, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1756
        $date = new \DateTime('2014-03-30T02:01:00+01:00');
1757
        $this->assertSame('Sunday, 30 March 2014, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1758
        // From 3AM to 2AM in 2014.
1759
        $date = new \DateTime('2014-10-26T01:59:00+01:00');
1760
        $this->assertSame('Sunday, 26 October 2014, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1761
        $date = new \DateTime('2014-10-26T02:01:00+01:00');
1762
        $this->assertSame('Sunday, 26 October 2014, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1763
        // From 2AM to 3AM in 2020.
1764
        $date = new \DateTime('2020-03-29T01:59:00+01:00');
1765
        $this->assertSame('Sunday, 29 March 2020, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1766
        $date = new \DateTime('2020-03-29T02:01:00+01:00');
1767
        $this->assertSame('Sunday, 29 March 2020, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1768
        // From 3AM to 2AM in 2020.
1769
        $date = new \DateTime('2020-10-25T01:59:00+01:00');
1770
        $this->assertSame('Sunday, 25 October 2020, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1771
        $date = new \DateTime('2020-10-25T02:01:00+01:00');
1772
        $this->assertSame('Sunday, 25 October 2020, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1773
 
1774
        // DST switching in NZ.
1775
        // From 3AM to 2AM in 2015.
1776
        $date = new \DateTime('2015-04-05T02:59:00+13:00');
1777
        $this->assertSame('Sunday, 5 April 2015, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Pacific/Auckland'));
1778
        $date = new \DateTime('2015-04-05T03:01:00+13:00');
1779
        $this->assertSame('Sunday, 5 April 2015, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Pacific/Auckland'));
1780
        // From 2AM to 3AM in 2009.
1781
        $date = new \DateTime('2015-09-27T01:59:00+12:00');
1782
        $this->assertSame('Sunday, 27 September 2015, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Pacific/Auckland'));
1783
        $date = new \DateTime('2015-09-27T02:01:00+12:00');
1784
        $this->assertSame('Sunday, 27 September 2015, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Pacific/Auckland'));
1785
 
1786
        // DST switching in Perth.
1787
        // From 3AM to 2AM in 2009.
1788
        $date = new \DateTime('2008-03-30T01:59:00+08:00');
1789
        $this->assertSame('Sunday, 30 March 2008, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Australia/Perth'));
1790
        $date = new \DateTime('2008-03-30T02:01:00+08:00');
1791
        $this->assertSame('Sunday, 30 March 2008, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Australia/Perth'));
1792
        // From 2AM to 3AM in 2009.
1793
        $date = new \DateTime('2008-10-26T01:59:00+08:00');
1794
        $this->assertSame('Sunday, 26 October 2008, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Australia/Perth'));
1795
        $date = new \DateTime('2008-10-26T02:01:00+08:00');
1796
        $this->assertSame('Sunday, 26 October 2008, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Australia/Perth'));
1797
 
1798
        // DST switching in US.
1799
        // From 2AM to 3AM in 2014.
1800
        $date = new \DateTime('2014-03-09T01:59:00-05:00');
1801
        $this->assertSame('Sunday, 9 March 2014, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'America/New_York'));
1802
        $date = new \DateTime('2014-03-09T02:01:00-05:00');
1803
        $this->assertSame('Sunday, 9 March 2014, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'America/New_York'));
1804
        // From 3AM to 2AM in 2014.
1805
        $date = new \DateTime('2014-11-02T01:59:00-04:00');
1806
        $this->assertSame('Sunday, 2 November 2014, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'America/New_York'));
1807
        $date = new \DateTime('2014-11-02T02:01:00-04:00');
1808
        $this->assertSame('Sunday, 2 November 2014, 01:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'America/New_York'));
1809
    }
1810
 
11 efrain 1811
    public function test_make_timestamp(): void {
1 efrain 1812
        global $USER, $CFG, $DB;
1813
        $this->resetAfterTest();
1814
 
1815
        $this->setAdminUser();
1816
 
1817
        $testvalues = array(
1818
            array(
1819
                'usertimezone' => 'America/Moncton',
1820
                'year' => '2011',
1821
                'month' => '7',
1822
                'day' => '1',
1823
                'hour' => '10',
1824
                'minutes' => '00',
1825
                'seconds' => '00',
1826
                'timezone' => '0.0',
1827
                'applydst' => false, // No dst offset.
1828
                'expectedoutput' => '1309514400' // 6pm at UTC+0.
1829
            ),
1830
            array(
1831
                'usertimezone' => 'America/Moncton',
1832
                'year' => '2011',
1833
                'month' => '7',
1834
                'day' => '1',
1835
                'hour' => '10',
1836
                'minutes' => '00',
1837
                'seconds' => '00',
1838
                'timezone' => '99',  // User default timezone.
1839
                'applydst' => false, // Don't apply dst.
1840
                'expectedoutput' => '1309528800'
1841
            ),
1842
            array(
1843
                'usertimezone' => 'America/Moncton',
1844
                'year' => '2011',
1845
                'month' => '7',
1846
                'day' => '1',
1847
                'hour' => '10',
1848
                'minutes' => '00',
1849
                'seconds' => '00',
1850
                'timezone' => '99', // User default timezone.
1851
                'applydst' => true, // Apply dst.
1852
                'expectedoutput' => '1309525200'
1853
            ),
1854
            array(
1855
                'usertimezone' => 'America/Moncton',
1856
                'year' => '2011',
1857
                'month' => '7',
1858
                'day' => '1',
1859
                'hour' => '10',
1860
                'minutes' => '00',
1861
                'seconds' => '00',
1862
                'timezone' => 'America/Moncton', // String timezone.
1863
                'applydst' => true, // Apply dst.
1864
                'expectedoutput' => '1309525200'
1865
            ),
1866
            array(
1867
                'usertimezone' => '2', // No dst applyed.
1868
                'year' => '2011',
1869
                'month' => '7',
1870
                'day' => '1',
1871
                'hour' => '10',
1872
                'minutes' => '00',
1873
                'seconds' => '00',
1874
                'timezone' => '99', // Take user timezone.
1875
                'applydst' => true, // Apply dst.
1876
                'expectedoutput' => '1309507200'
1877
            ),
1878
            array(
1879
                'usertimezone' => '-2', // No dst applyed.
1880
                'year' => '2011',
1881
                'month' => '7',
1882
                'day' => '1',
1883
                'hour' => '10',
1884
                'minutes' => '00',
1885
                'seconds' => '00',
1886
                'timezone' => '99', // Take usertimezone.
1887
                'applydst' => true, // Apply dst.
1888
                'expectedoutput' => '1309521600'
1889
            ),
1890
            array(
1891
                'usertimezone' => '-10', // No dst applyed.
1892
                'year' => '2011',
1893
                'month' => '7',
1894
                'day' => '1',
1895
                'hour' => '10',
1896
                'minutes' => '00',
1897
                'seconds' => '00',
1898
                'timezone' => '2',  // Take this timezone.
1899
                'applydst' => true, // Apply dst.
1900
                'expectedoutput' => '1309507200'
1901
            ),
1902
            array(
1903
                'usertimezone' => '-10', // No dst applyed.
1904
                'year' => '2011',
1905
                'month' => '7',
1906
                'day' => '1',
1907
                'hour' => '10',
1908
                'minutes' => '00',
1909
                'seconds' => '00',
1910
                'timezone' => '-2', // Take this timezone.
1911
                'applydst' => true, // Apply dst.
1912
                'expectedoutput' => '1309521600'
1913
            ),
1914
            array(
1915
                'usertimezone' => '-10', // No dst applyed.
1916
                'year' => '2011',
1917
                'month' => '7',
1918
                'day' => '1',
1919
                'hour' => '10',
1920
                'minutes' => '00',
1921
                'seconds' => '00',
1922
                'timezone' => 'random/time', // This should show server time.
1923
                'applydst' => true,          // Apply dst.
1924
                'expectedoutput' => '1309485600'
1925
            ),
1926
            array(
1927
                'usertimezone' => '-14', // Server time.
1928
                'year' => '2011',
1929
                'month' => '7',
1930
                'day' => '1',
1931
                'hour' => '10',
1932
                'minutes' => '00',
1933
                'seconds' => '00',
1934
                'timezone' => '99', // Get user time.
1935
                'applydst' => true, // Apply dst.
1936
                'expectedoutput' => '1309485600'
1937
            )
1938
        );
1939
 
1940
        // Set default timezone to Australia/Perth, else time calculated
1941
        // will not match expected values.
1942
        $this->setTimezone(99, 'Australia/Perth');
1943
 
1944
        // Test make_timestamp with all testvals and assert if anything wrong.
1945
        foreach ($testvalues as $vals) {
1946
            $USER->timezone = $vals['usertimezone'];
1947
            $actualoutput = make_timestamp(
1948
                $vals['year'],
1949
                $vals['month'],
1950
                $vals['day'],
1951
                $vals['hour'],
1952
                $vals['minutes'],
1953
                $vals['seconds'],
1954
                $vals['timezone'],
1955
                $vals['applydst']
1956
            );
1957
 
1958
            // On different systems case of AM PM changes so compare case insensitive.
1959
            $vals['expectedoutput'] = \core_text::strtolower($vals['expectedoutput']);
1960
            $actualoutput = \core_text::strtolower($actualoutput);
1961
 
1962
            $this->assertSame($vals['expectedoutput'], $actualoutput,
1963
                "Expected: {$vals['expectedoutput']} => Actual: {$actualoutput},
1964
                Please check if timezones are updated (Site adminstration -> location -> update timezone)");
1965
        }
1966
    }
1967
 
1968
    /**
1969
     * Test get_string and most importantly the implementation of the lang_string
1970
     * object.
1971
     */
11 efrain 1972
    public function test_get_string(): void {
1 efrain 1973
        global $COURSE;
1974
 
1975
        // Make sure we are using English.
1976
        $originallang = $COURSE->lang;
1977
        $COURSE->lang = 'en';
1978
 
1979
        $yes = get_string('yes');
1980
        $yesexpected = 'Yes';
1981
        $this->assertIsString($yes);
1982
        $this->assertSame($yesexpected, $yes);
1983
 
1984
        $yes = get_string('yes', 'moodle');
1985
        $this->assertIsString($yes);
1986
        $this->assertSame($yesexpected, $yes);
1987
 
1988
        $yes = get_string('yes', 'core');
1989
        $this->assertIsString($yes);
1990
        $this->assertSame($yesexpected, $yes);
1991
 
1992
        $yes = get_string('yes', '');
1993
        $this->assertIsString($yes);
1994
        $this->assertSame($yesexpected, $yes);
1995
 
1996
        $yes = get_string('yes', null);
1997
        $this->assertIsString($yes);
1998
        $this->assertSame($yesexpected, $yes);
1999
 
2000
        $yes = get_string('yes', null, 1);
2001
        $this->assertIsString($yes);
2002
        $this->assertSame($yesexpected, $yes);
2003
 
2004
        $days = 1;
2005
        $numdays = get_string('numdays', 'core', '1');
2006
        $numdaysexpected = $days.' days';
2007
        $this->assertIsString($numdays);
2008
        $this->assertSame($numdaysexpected, $numdays);
2009
 
2010
        $yes = get_string('yes', null, null, true);
2011
        $this->assertInstanceOf('lang_string', $yes);
2012
        $this->assertSame($yesexpected, (string)$yes);
2013
 
2014
        // Test lazy loading (returning lang_string) correctly interpolates 0 being used as args.
2015
        $numdays = get_string('numdays', 'moodle', 0, true);
2016
        $this->assertInstanceOf(lang_string::class, $numdays);
2017
        $this->assertSame('0 days', (string) $numdays);
2018
 
2019
        // Test using a lang_string object as the $a argument for a normal
2020
        // get_string call (returning string).
2021
        $test = new lang_string('yes', null, null, true);
2022
        $testexpected = get_string('numdays', 'core', get_string('yes'));
2023
        $testresult = get_string('numdays', null, $test);
2024
        $this->assertIsString($testresult);
2025
        $this->assertSame($testexpected, $testresult);
2026
 
2027
        // Test using a lang_string object as the $a argument for an object
2028
        // get_string call (returning lang_string).
2029
        $test = new lang_string('yes', null, null, true);
2030
        $testexpected = get_string('numdays', 'core', get_string('yes'));
2031
        $testresult = get_string('numdays', null, $test, true);
2032
        $this->assertInstanceOf('lang_string', $testresult);
2033
        $this->assertSame($testexpected, "$testresult");
2034
 
2035
        // Make sure that object properties that can't be converted don't cause
2036
        // errors.
2037
        // Level one: This is as deep as current language processing goes.
2038
        $test = new \stdClass;
2039
        $test->one = 'here';
2040
        $string = get_string('yes', null, $test, true);
2041
        $this->assertEquals($yesexpected, $string);
2042
 
2043
        // Make sure that object properties that can't be converted don't cause
2044
        // errors.
2045
        // Level two: Language processing doesn't currently reach this deep.
2046
        // only immediate scalar properties are worked with.
2047
        $test = new \stdClass;
2048
        $test->one = new \stdClass;
2049
        $test->one->two = 'here';
2050
        $string = get_string('yes', null, $test, true);
2051
        $this->assertEquals($yesexpected, $string);
2052
 
2053
        // Make sure that object properties that can't be converted don't cause
2054
        // errors.
2055
        // Level three: It should never ever go this deep, but we're making sure
2056
        // it doesn't cause any probs anyway.
2057
        $test = new \stdClass;
2058
        $test->one = new \stdClass;
2059
        $test->one->two = new \stdClass;
2060
        $test->one->two->three = 'here';
2061
        $string = get_string('yes', null, $test, true);
2062
        $this->assertEquals($yesexpected, $string);
2063
 
2064
        // Make sure that object properties that can't be converted don't cause
2065
        // errors and check lang_string properties.
2066
        // Level one: This is as deep as current language processing goes.
2067
        $test = new \stdClass;
2068
        $test->one = new lang_string('yes');
2069
        $string = get_string('yes', null, $test, true);
2070
        $this->assertEquals($yesexpected, $string);
2071
 
2072
        // Make sure that object properties that can't be converted don't cause
2073
        // errors and check lang_string properties.
2074
        // Level two: Language processing doesn't currently reach this deep.
2075
        // only immediate scalar properties are worked with.
2076
        $test = new \stdClass;
2077
        $test->one = new \stdClass;
2078
        $test->one->two = new lang_string('yes');
2079
        $string = get_string('yes', null, $test, true);
2080
        $this->assertEquals($yesexpected, $string);
2081
 
2082
        // Make sure that object properties that can't be converted don't cause
2083
        // errors and check lang_string properties.
2084
        // Level three: It should never ever go this deep, but we're making sure
2085
        // it doesn't cause any probs anyway.
2086
        $test = new \stdClass;
2087
        $test->one = new \stdClass;
2088
        $test->one->two = new \stdClass;
2089
        $test->one->two->three = new lang_string('yes');
2090
        $string = get_string('yes', null, $test, true);
2091
        $this->assertEquals($yesexpected, $string);
2092
 
2093
        // Make sure that array properties that can't be converted don't cause
2094
        // errors.
2095
        $test = array();
2096
        $test['one'] = new \stdClass;
2097
        $test['one']->two = 'here';
2098
        $string = get_string('yes', null, $test, true);
2099
        $this->assertEquals($yesexpected, $string);
2100
 
2101
        // Same thing but as above except using an object... this is allowed :P.
2102
        $string = get_string('yes', null, null, true);
2103
        $object = new \stdClass;
2104
        $object->$string = 'Yes';
2105
        $this->assertEquals($yesexpected, $string);
2106
        $this->assertEquals($yesexpected, $object->$string);
2107
 
2108
        // Reset the language.
2109
        $COURSE->lang = $originallang;
2110
    }
2111
 
11 efrain 2112
    public function test_lang_string_var_export(): void {
1 efrain 2113
 
2114
        // Call var_export() on a newly generated lang_string.
2115
        $str = new lang_string('no');
2116
 
2117
        // In PHP 8.2 exported class names are now fully qualified;
2118
        // previously, the leading backslash was omitted.
2119
        $leadingbackslash = (version_compare(PHP_VERSION, '8.2.0', '>=')) ? '\\' : '';
2120
 
2121
        $expected1 = <<<EOF
1441 ariadna 2122
{$leadingbackslash}core\lang_string::__set_state(array(
1 efrain 2123
   'component' => 'moodle',
2124
   'a' => NULL,
2125
   'string' => NULL,
2126
   'forcedstring' => false,
1441 ariadna 2127
   'identifier' => 'no',
2128
   'lang' => NULL,
1 efrain 2129
))
2130
EOF;
2131
 
2132
        $v = var_export($str, true);
2133
        $this->assertEquals($expected1, $v);
2134
 
2135
        // Now execute the code that was returned - it should produce a correct string.
2136
        $str = lang_string::__set_state(array(
2137
            'identifier' => 'no',
2138
            'component' => 'moodle',
2139
            'a' => NULL,
2140
            'lang' => NULL,
2141
            'string' => NULL,
2142
            'forcedstring' => false,
2143
        ));
2144
 
2145
        $this->assertInstanceOf(lang_string::class, $str);
2146
        $this->assertEquals('No', $str);
2147
    }
2148
 
11 efrain 2149
    public function test_get_string_limitation(): void {
1 efrain 2150
        // This is one of the limitations to the lang_string class. It can't be
2151
        // used as a key.
2152
        $this->expectException(\TypeError::class);
2153
        $array = array(get_string('yes', null, null, true) => 'yes');
2154
    }
2155
 
2156
    /**
2157
     * Test localised float formatting.
2158
     */
11 efrain 2159
    public function test_format_float(): void {
1 efrain 2160
 
2161
        // Special case for null.
2162
        $this->assertEquals('', format_float(null));
2163
 
2164
        // Default 1 decimal place.
2165
        $this->assertEquals('5.4', format_float(5.43));
2166
        $this->assertEquals('5.0', format_float(5.001));
2167
 
2168
        // Custom number of decimal places.
2169
        $this->assertEquals('5.43000', format_float(5.43, 5));
2170
 
2171
        // Auto detect the number of decimal places.
2172
        $this->assertEquals('5.43', format_float(5.43, -1));
2173
        $this->assertEquals('5.43', format_float(5.43000, -1));
2174
        $this->assertEquals('5', format_float(5, -1));
2175
        $this->assertEquals('5', format_float(5.0, -1));
2176
        $this->assertEquals('0.543', format_float('5.43e-1', -1));
2177
        $this->assertEquals('0.543', format_float('5.43000e-1', -1));
2178
 
2179
        // Option to strip ending zeros after rounding.
2180
        $this->assertEquals('5.43', format_float(5.43, 5, true, true));
2181
        $this->assertEquals('5', format_float(5.0001, 3, true, true));
2182
        $this->assertEquals('100', format_float(100, 2, true, true));
2183
        $this->assertEquals('100', format_float(100, 0, true, true));
2184
 
2185
        // Tests with a localised decimal separator.
2186
        $this->define_local_decimal_separator();
2187
 
2188
        // Localisation on (default).
2189
        $this->assertEquals('5X43000', format_float(5.43, 5));
2190
        $this->assertEquals('5X43', format_float(5.43, 5, true, true));
2191
 
2192
        // Localisation off.
2193
        $this->assertEquals('5.43000', format_float(5.43, 5, false));
2194
        $this->assertEquals('5.43', format_float(5.43, 5, false, true));
2195
 
2196
        // Tests with tilde as localised decimal separator.
2197
        $this->define_local_decimal_separator('~');
2198
 
2199
        // Must also work for '~' as decimal separator.
2200
        $this->assertEquals('5', format_float(5.0001, 3, true, true));
2201
        $this->assertEquals('5~43000', format_float(5.43, 5));
2202
        $this->assertEquals('5~43', format_float(5.43, 5, true, true));
2203
    }
2204
 
2205
    /**
2206
     * Test localised float unformatting.
2207
     */
11 efrain 2208
    public function test_unformat_float(): void {
1 efrain 2209
 
2210
        // Tests without the localised decimal separator.
2211
 
2212
        // Special case for null, empty or white spaces only strings.
2213
        $this->assertEquals(null, unformat_float(null));
2214
        $this->assertEquals(null, unformat_float(''));
2215
        $this->assertEquals(null, unformat_float('    '));
2216
 
2217
        // Regular use.
2218
        $this->assertEquals(5.4, unformat_float('5.4'));
2219
        $this->assertEquals(5.4, unformat_float('5.4', true));
2220
 
2221
        // No decimal.
2222
        $this->assertEquals(5.0, unformat_float('5'));
2223
 
2224
        // Custom number of decimal.
2225
        $this->assertEquals(5.43267, unformat_float('5.43267'));
2226
 
2227
        // Empty decimal.
2228
        $this->assertEquals(100.0, unformat_float('100.00'));
2229
 
2230
        // With the thousand separator.
2231
        $this->assertEquals(1000.0, unformat_float('1 000'));
2232
        $this->assertEquals(1000.32, unformat_float('1 000.32'));
2233
 
2234
        // Negative number.
2235
        $this->assertEquals(-100.0, unformat_float('-100'));
2236
 
2237
        // Wrong value.
2238
        $this->assertEquals(0.0, unformat_float('Wrong value'));
2239
        // Wrong value in strict mode.
2240
        $this->assertFalse(unformat_float('Wrong value', true));
2241
 
2242
        // Combining options.
2243
        $this->assertEquals(-1023.862567, unformat_float('   -1 023.862567     '));
2244
 
2245
        // Bad decimal separator (should crop the decimal).
2246
        $this->assertEquals(50.0, unformat_float('50,57'));
2247
        // Bad decimal separator in strict mode (should return false).
2248
        $this->assertFalse(unformat_float('50,57', true));
2249
 
2250
        // Tests with a localised decimal separator.
2251
        $this->define_local_decimal_separator();
2252
 
2253
        // We repeat the tests above but with the current decimal separator.
2254
 
2255
        // Regular use without and with the localised separator.
2256
        $this->assertEquals (5.4, unformat_float('5.4'));
2257
        $this->assertEquals (5.4, unformat_float('5X4'));
2258
 
2259
        // Custom number of decimal.
2260
        $this->assertEquals (5.43267, unformat_float('5X43267'));
2261
 
2262
        // Empty decimal.
2263
        $this->assertEquals (100.0, unformat_float('100X00'));
2264
 
2265
        // With the thousand separator.
2266
        $this->assertEquals (1000.32, unformat_float('1 000X32'));
2267
 
2268
        // Bad different separator (should crop the decimal).
2269
        $this->assertEquals (50.0, unformat_float('50Y57'));
2270
        // Bad different separator in strict mode (should return false).
2271
        $this->assertFalse (unformat_float('50Y57', true));
2272
 
2273
        // Combining options.
2274
        $this->assertEquals (-1023.862567, unformat_float('   -1 023X862567     '));
2275
        // Combining options in strict mode.
2276
        $this->assertEquals (-1023.862567, unformat_float('   -1 023X862567     ', true));
2277
    }
2278
 
2279
    /**
2280
     * Test deleting of users.
2281
     */
11 efrain 2282
    public function test_delete_user(): void {
1 efrain 2283
        global $DB, $CFG;
2284
 
2285
        $this->resetAfterTest();
2286
 
2287
        $guest = $DB->get_record('user', array('id'=>$CFG->siteguest), '*', MUST_EXIST);
2288
        $admin = $DB->get_record('user', array('id'=>$CFG->siteadmins), '*', MUST_EXIST);
2289
        $this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
2290
 
2291
        $user = $this->getDataGenerator()->create_user(array('idnumber'=>'abc'));
2292
        $user2 = $this->getDataGenerator()->create_user(array('idnumber'=>'xyz'));
2293
        $usersharedemail1 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
2294
        $usersharedemail2 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
2295
        $useremptyemail1 = $this->getDataGenerator()->create_user(array('email' => ''));
2296
        $useremptyemail2 = $this->getDataGenerator()->create_user(array('email' => ''));
2297
 
2298
        // Delete user and capture event.
2299
        $sink = $this->redirectEvents();
2300
        $result = delete_user($user);
2301
        $events = $sink->get_events();
2302
        $sink->close();
2303
        $event = array_pop($events);
2304
 
2305
        // Test user is deleted in DB.
2306
        $this->assertTrue($result);
2307
        $deluser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
2308
        $this->assertEquals(1, $deluser->deleted);
2309
        $this->assertEquals(0, $deluser->picture);
2310
        $this->assertSame('', $deluser->idnumber);
2311
        $this->assertSame(md5($user->username), $deluser->email);
2312
        $this->assertMatchesRegularExpression('/^'.preg_quote($user->email, '/').'\.\d*$/', $deluser->username);
2313
 
2314
        $this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
2315
 
2316
        // Test Event.
2317
        $this->assertInstanceOf('\core\event\user_deleted', $event);
2318
        $this->assertSame($user->id, $event->objectid);
2319
        $eventdata = $event->get_data();
2320
        $this->assertSame($eventdata['other']['username'], $user->username);
2321
        $this->assertSame($eventdata['other']['email'], $user->email);
2322
        $this->assertSame($eventdata['other']['idnumber'], $user->idnumber);
2323
        $this->assertSame($eventdata['other']['picture'], $user->picture);
2324
        $this->assertSame($eventdata['other']['mnethostid'], $user->mnethostid);
2325
        $this->assertEquals($user, $event->get_record_snapshot('user', $event->objectid));
2326
        $this->assertEventContextNotUsed($event);
2327
 
2328
        // Try invalid params.
2329
        $record = new \stdClass();
2330
        $record->grrr = 1;
2331
        try {
2332
            delete_user($record);
2333
            $this->fail('Expecting exception for invalid delete_user() $user parameter');
2334
        } catch (\moodle_exception $ex) {
2335
            $this->assertInstanceOf('coding_exception', $ex);
2336
        }
2337
        $record->id = 1;
2338
        try {
2339
            delete_user($record);
2340
            $this->fail('Expecting exception for invalid delete_user() $user parameter');
2341
        } catch (\moodle_exception $ex) {
2342
            $this->assertInstanceOf('coding_exception', $ex);
2343
        }
2344
 
2345
        $record = new \stdClass();
2346
        $record->id = 666;
2347
        $record->username = 'xx';
2348
        $this->assertFalse($DB->record_exists('user', array('id'=>666))); // Any non-existent id is ok.
2349
        $result = delete_user($record);
2350
        $this->assertFalse($result);
2351
 
2352
        $result = delete_user($guest);
2353
        $this->assertFalse($result);
2354
 
2355
        $result = delete_user($admin);
2356
        $this->assertFalse($result);
2357
 
2358
        // Simultaneously deleting users with identical email addresses.
2359
        $result1 = delete_user($usersharedemail1);
2360
        $result2 = delete_user($usersharedemail2);
2361
 
2362
        $usersharedemail1after = $DB->get_record('user', array('id' => $usersharedemail1->id));
2363
        $usersharedemail2after = $DB->get_record('user', array('id' => $usersharedemail2->id));
2364
        $this->assertTrue($result1);
2365
        $this->assertTrue($result2);
2366
        $this->assertStringStartsWith($usersharedemail1->email . '.', $usersharedemail1after->username);
2367
        $this->assertStringStartsWith($usersharedemail2->email . '.', $usersharedemail2after->username);
2368
 
2369
        // Simultaneously deleting users without email addresses.
2370
        $result1 = delete_user($useremptyemail1);
2371
        $result2 = delete_user($useremptyemail2);
2372
 
2373
        $useremptyemail1after = $DB->get_record('user', array('id' => $useremptyemail1->id));
2374
        $useremptyemail2after = $DB->get_record('user', array('id' => $useremptyemail2->id));
2375
        $this->assertTrue($result1);
2376
        $this->assertTrue($result2);
2377
        $this->assertStringStartsWith($useremptyemail1->username . '.' . $useremptyemail1->id . '@unknownemail.invalid.',
2378
            $useremptyemail1after->username);
2379
        $this->assertStringStartsWith($useremptyemail2->username . '.' . $useremptyemail2->id . '@unknownemail.invalid.',
2380
            $useremptyemail2after->username);
2381
 
2382
        $this->resetDebugging();
2383
    }
2384
 
2385
    /**
2386
     * Test deletion of user with long username
2387
     */
11 efrain 2388
    public function test_delete_user_long_username(): void {
1 efrain 2389
        global $DB;
2390
 
2391
        $this->resetAfterTest();
2392
 
2393
        // For users without an e-mail, one will be created during deletion using {$username}.{$id}@unknownemail.invalid format.
2394
        $user = $this->getDataGenerator()->create_user([
2395
            'username' => str_repeat('a', 75),
2396
            'email' => '',
2397
        ]);
2398
 
2399
        delete_user($user);
2400
 
2401
        // The username for the deleted user shouldn't exceed 100 characters.
2402
        $usernamedeleted = $DB->get_field('user', 'username', ['id' => $user->id]);
2403
        $this->assertEquals(100, \core_text::strlen($usernamedeleted));
2404
 
2405
        $timestrlength = \core_text::strlen((string) time());
2406
 
2407
        // It should start with the user name, and end with the current time.
2408
        $this->assertStringStartsWith("{$user->username}.{$user->id}@", $usernamedeleted);
2409
        $this->assertMatchesRegularExpression('/\.\d{' . $timestrlength . '}$/', $usernamedeleted);
2410
    }
2411
 
2412
    /**
2413
     * Test deletion of user with long email address
2414
     */
11 efrain 2415
    public function test_delete_user_long_email(): void {
1 efrain 2416
        global $DB;
2417
 
2418
        $this->resetAfterTest();
2419
 
2420
        // Create user with 90 character email address.
2421
        $user = $this->getDataGenerator()->create_user([
2422
            'email' => str_repeat('a', 78) . '@example.com',
2423
        ]);
2424
 
2425
        delete_user($user);
2426
 
2427
        // The username for the deleted user shouldn't exceed 100 characters.
2428
        $usernamedeleted = $DB->get_field('user', 'username', ['id' => $user->id]);
2429
        $this->assertEquals(100, \core_text::strlen($usernamedeleted));
2430
 
2431
        $timestrlength = \core_text::strlen((string) time());
2432
 
2433
        // Max username length is 100 chars. Select up to limit - (length of current time + 1 [period character]) from users email.
2434
        $expectedemail = \core_text::substr($user->email, 0, 100 - ($timestrlength + 1));
2435
        $this->assertMatchesRegularExpression('/^' . preg_quote($expectedemail) . '\.\d{' . $timestrlength . '}$/',
2436
            $usernamedeleted);
2437
    }
2438
 
2439
    /**
2440
     * Test function convert_to_array()
2441
     */
11 efrain 2442
    public function test_convert_to_array(): void {
1 efrain 2443
        // Check that normal classes are converted to arrays the same way as (array) would do.
2444
        $obj = new \stdClass();
2445
        $obj->prop1 = 'hello';
2446
        $obj->prop2 = array('first', 'second', 13);
2447
        $obj->prop3 = 15;
2448
        $this->assertEquals(convert_to_array($obj), (array)$obj);
2449
 
2450
        // Check that context object (with iterator) is converted to array properly.
2451
        $obj = \context_system::instance();
2452
        $ar = array(
2453
            'id'           => $obj->id,
2454
            'contextlevel' => $obj->contextlevel,
2455
            'instanceid'   => $obj->instanceid,
2456
            'path'         => $obj->path,
2457
            'depth'        => $obj->depth,
2458
            'locked'       => $obj->locked,
2459
        );
2460
        $this->assertEquals(convert_to_array($obj), $ar);
2461
    }
2462
 
2463
    /**
2464
     * Test the function date_format_string().
2465
     */
11 efrain 2466
    public function test_date_format_string(): void {
1 efrain 2467
        global $CFG;
2468
 
2469
        $this->resetAfterTest();
2470
        $this->setTimezone(99, 'Australia/Perth');
2471
 
2472
        $tests = array(
2473
            array(
2474
                'tz' => 99,
2475
                'str' => '%A, %d %B %Y, %I:%M %p',
2476
                'expected' => 'Saturday, 01 January 2011, 06:00 PM'
2477
            ),
2478
            array(
2479
                'tz' => 0,
2480
                'str' => '%A, %d %B %Y, %I:%M %p',
2481
                'expected' => 'Saturday, 01 January 2011, 10:00 AM'
2482
            ),
2483
            array(
2484
                // Note: this function expected the timestamp in weird format before,
2485
                // since 2.9 it uses UTC.
2486
                'tz' => 'Pacific/Auckland',
2487
                'str' => '%A, %d %B %Y, %I:%M %p',
2488
                'expected' => 'Saturday, 01 January 2011, 11:00 PM'
2489
            ),
2490
            // Following tests pass on Windows only because en lang pack does
2491
            // not contain localewincharset, in real life lang pack maintainers
2492
            // may use only characters that are present in localewincharset
2493
            // in format strings!
2494
            array(
2495
                'tz' => 99,
2496
                'str' => 'Žluťoučký koníček %A',
2497
                'expected' => 'Žluťoučký koníček Saturday'
2498
            ),
2499
            array(
2500
                'tz' => 99,
2501
                'str' => '言語設定言語 %A',
2502
                'expected' => '言語設定言語 Saturday'
2503
            ),
2504
            array(
2505
                'tz' => 99,
2506
                'str' => '简体中文简体 %A',
2507
                'expected' => '简体中文简体 Saturday'
2508
            ),
2509
        );
2510
 
2511
        // Note: date_format_string() uses the timezone only to differenciate
2512
        // the server time from the UTC time. It does not modify the timestamp.
2513
        // Hence similar results for timezones <= 13.
2514
        // On different systems case of AM PM changes so compare case insensitive.
2515
        foreach ($tests as $test) {
2516
            $str = date_format_string(1293876000, $test['str'], $test['tz']);
2517
            $this->assertSame(\core_text::strtolower($test['expected']), \core_text::strtolower($str));
2518
        }
2519
    }
2520
 
11 efrain 2521
    public function test_get_config(): void {
1 efrain 2522
        global $CFG;
2523
 
2524
        $this->resetAfterTest();
2525
 
2526
        // Preparation.
2527
        set_config('phpunit_test_get_config_1', 'test 1');
2528
        set_config('phpunit_test_get_config_2', 'test 2', 'mod_forum');
2529
        if (!is_array($CFG->config_php_settings)) {
2530
            $CFG->config_php_settings = array();
2531
        }
2532
        $CFG->config_php_settings['phpunit_test_get_config_3'] = 'test 3';
2533
 
2534
        if (!is_array($CFG->forced_plugin_settings)) {
2535
            $CFG->forced_plugin_settings = array();
2536
        }
2537
        if (!array_key_exists('mod_forum', $CFG->forced_plugin_settings)) {
2538
            $CFG->forced_plugin_settings['mod_forum'] = array();
2539
        }
2540
        $CFG->forced_plugin_settings['mod_forum']['phpunit_test_get_config_4'] = 'test 4';
2541
        $CFG->phpunit_test_get_config_5 = 'test 5';
2542
 
2543
        // Testing.
2544
        $this->assertSame('test 1', get_config('core', 'phpunit_test_get_config_1'));
2545
        $this->assertSame('test 2', get_config('mod_forum', 'phpunit_test_get_config_2'));
2546
        $this->assertSame('test 3', get_config('core', 'phpunit_test_get_config_3'));
2547
        $this->assertSame('test 4', get_config('mod_forum', 'phpunit_test_get_config_4'));
2548
        $this->assertFalse(get_config('core', 'phpunit_test_get_config_5'));
2549
        $this->assertFalse(get_config('core', 'phpunit_test_get_config_x'));
2550
        $this->assertFalse(get_config('mod_forum', 'phpunit_test_get_config_x'));
2551
 
2552
        // Test config we know to exist.
2553
        $this->assertSame($CFG->dataroot, get_config('core', 'dataroot'));
2554
        $this->assertSame($CFG->phpunit_dataroot, get_config('core', 'phpunit_dataroot'));
2555
        $this->assertSame($CFG->dataroot, get_config('core', 'phpunit_dataroot'));
2556
        $this->assertSame(get_config('core', 'dataroot'), get_config('core', 'phpunit_dataroot'));
2557
 
2558
        // Test setting a config var that already exists.
2559
        set_config('phpunit_test_get_config_1', 'test a');
2560
        $this->assertSame('test a', $CFG->phpunit_test_get_config_1);
2561
        $this->assertSame('test a', get_config('core', 'phpunit_test_get_config_1'));
2562
 
2563
        // Test cache invalidation.
2564
        $cache = \cache::make('core', 'config');
2565
        $this->assertIsArray($cache->get('core'));
2566
        $this->assertIsArray($cache->get('mod_forum'));
2567
        set_config('phpunit_test_get_config_1', 'test b');
2568
        $this->assertFalse($cache->get('core'));
2569
        set_config('phpunit_test_get_config_4', 'test c', 'mod_forum');
2570
        $this->assertFalse($cache->get('mod_forum'));
2571
    }
2572
 
11 efrain 2573
    public function test_get_max_upload_sizes(): void {
1 efrain 2574
        // Test with very low limits so we are not affected by php upload limits.
2575
        // Test activity limit smallest.
2576
        $sitebytes = 102400;
2577
        $coursebytes = 51200;
2578
        $modulebytes = 10240;
2579
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2580
 
2581
        $nbsp = "\xc2\xa0";
2582
        $this->assertSame("Activity upload limit (10{$nbsp}KB)", $result['0']);
2583
        $this->assertCount(2, $result);
2584
 
2585
        // Test course limit smallest.
2586
        $sitebytes = 102400;
2587
        $coursebytes = 10240;
2588
        $modulebytes = 51200;
2589
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2590
 
2591
        $this->assertSame("Course upload limit (10{$nbsp}KB)", $result['0']);
2592
        $this->assertCount(2, $result);
2593
 
2594
        // Test site limit smallest.
2595
        $sitebytes = 10240;
2596
        $coursebytes = 102400;
2597
        $modulebytes = 51200;
2598
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2599
 
2600
        $this->assertSame("Site upload limit (10{$nbsp}KB)", $result['0']);
2601
        $this->assertCount(2, $result);
2602
 
2603
        // Test site limit not set.
2604
        $sitebytes = 0;
2605
        $coursebytes = 102400;
2606
        $modulebytes = 51200;
2607
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2608
 
2609
        $this->assertSame("Activity upload limit (50{$nbsp}KB)", $result['0']);
2610
        $this->assertCount(3, $result);
2611
 
2612
        $sitebytes = 0;
2613
        $coursebytes = 51200;
2614
        $modulebytes = 102400;
2615
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2616
 
2617
        $this->assertSame("Course upload limit (50{$nbsp}KB)", $result['0']);
2618
        $this->assertCount(3, $result);
2619
 
2620
        // Test custom bytes in range.
2621
        $sitebytes = 102400;
2622
        $coursebytes = 51200;
2623
        $modulebytes = 51200;
2624
        $custombytes = 10240;
2625
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2626
 
2627
        $this->assertCount(3, $result);
2628
 
2629
        // Test custom bytes in range but non-standard.
2630
        $sitebytes = 102400;
2631
        $coursebytes = 51200;
2632
        $modulebytes = 51200;
2633
        $custombytes = 25600;
2634
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2635
 
2636
        $this->assertCount(4, $result);
2637
 
2638
        // Test custom bytes out of range.
2639
        $sitebytes = 102400;
2640
        $coursebytes = 51200;
2641
        $modulebytes = 51200;
2642
        $custombytes = 102400;
2643
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2644
 
2645
        $this->assertCount(3, $result);
2646
 
2647
        // Test custom bytes out of range and non-standard.
2648
        $sitebytes = 102400;
2649
        $coursebytes = 51200;
2650
        $modulebytes = 51200;
2651
        $custombytes = 256000;
2652
        $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2653
 
2654
        $this->assertCount(3, $result);
2655
 
2656
        // Test site limit only.
2657
        $sitebytes = 51200;
2658
        $result = get_max_upload_sizes($sitebytes);
2659
 
2660
        $this->assertSame("Site upload limit (50{$nbsp}KB)", $result['0']);
2661
        $this->assertSame("50{$nbsp}KB", $result['51200']);
2662
        $this->assertSame("10{$nbsp}KB", $result['10240']);
2663
        $this->assertCount(3, $result);
2664
 
2665
        // Test no limit.
2666
        $result = get_max_upload_sizes();
2667
        $this->assertArrayHasKey('0', $result);
2668
        $this->assertArrayHasKey(get_max_upload_file_size(), $result);
2669
    }
2670
 
2671
    /**
2672
     * Test function password_is_legacy_hash.
2673
     * @covers ::password_is_legacy_hash
2674
     */
11 efrain 2675
    public function test_password_is_legacy_hash(): void {
1 efrain 2676
        // Well formed bcrypt hashes should be matched.
2677
        foreach (array('some', 'strings', 'to_check!') as $password) {
2678
            $bcrypt = password_hash($password, '2y');
2679
            $this->assertTrue(password_is_legacy_hash($bcrypt));
2680
        }
2681
        // Strings that are not bcrypt should not be matched.
2682
        $sha512 = '$6$rounds=5000$somesalt$9nEA35u5h4oDrUdcVFUwXDSwIBiZtuKDHiaI/kxnBSslH4wVXeAhVsDn1UFxBxrnRJva/8dZ8IouaijJdd4cF';
2683
        foreach (array('', AUTH_PASSWORD_NOT_CACHED, $sha512) as $notbcrypt) {
2684
            $this->assertFalse(password_is_legacy_hash($notbcrypt));
2685
        }
2686
    }
2687
 
2688
    /**
2689
     * Test function that calculates password pepper entropy.
2690
     * @covers ::calculate_entropy
2691
     */
11 efrain 2692
    public function test_calculate_entropy(): void {
1 efrain 2693
        // Test that the function returns 0 with an empty string.
2694
        $this->assertEquals(0, calculate_entropy(''));
2695
 
2696
        // Test that the function returns the correct entropy.
2697
        $this->assertEquals(132.8814, number_format(calculate_entropy('#GV]NLie|x$H9[$rW%94bXZvJHa%z'), 4));
2698
    }
2699
 
2700
    /**
2701
     * Test function to get password peppers.
2702
     * @covers ::get_password_peppers
2703
     */
11 efrain 2704
    public function test_get_password_peppers(): void {
1 efrain 2705
        global $CFG;
2706
        $this->resetAfterTest();
2707
 
2708
        // First assert that the function returns an empty array,
2709
        // when no peppers are set.
2710
        $this->assertEquals([], get_password_peppers());
2711
 
2712
        // Now set some peppers and check that they are returned.
2713
        $CFG->passwordpeppers = [
2714
                1 => '#GV]NLie|x$H9[$rW%94bXZvJHa%z',
2715
                2 => '#GV]NLie|x$H9[$rW%94bXZvJHa%$'
2716
        ];
2717
        $peppers = get_password_peppers();
2718
        $this->assertCount(2, $peppers);
2719
        $this->assertEquals($CFG->passwordpeppers, $peppers);
2720
 
2721
        // Check that the peppers are returned in the correct order.
2722
        // Highest numerical key first.
2723
        $this->assertEquals('#GV]NLie|x$H9[$rW%94bXZvJHa%$', $peppers[2]);
2724
        $this->assertEquals('#GV]NLie|x$H9[$rW%94bXZvJHa%z', $peppers[1]);
2725
 
2726
        // Update the latest pepper to be an empty string,
2727
        // to test phasing out peppers.
2728
        $CFG->passwordpeppers = [
2729
                1 => '#GV]NLie|x$H9[$rW%94bXZvJHa%z',
2730
                2 => '#GV]NLie|x$H9[$rW%94bXZvJHa%$',
2731
                3 => ''
2732
        ];
2733
        $peppers = get_password_peppers();
2734
        $this->assertCount(3, $peppers);
2735
        $this->assertEquals($CFG->passwordpeppers, $peppers);
2736
 
2737
        // Finally, check that low entropy peppers throw an exception.
2738
        $CFG->passwordpeppers = [
2739
                1 => 'foo',
2740
                2 => 'bar'
2741
        ];
2742
        $this->expectException(\coding_exception::class);
2743
        get_password_peppers();
2744
    }
2745
 
2746
    /**
2747
     * Test function to validate password length.
2748
     *
2749
     * @covers ::exceeds_password_length
2750
     * @return void
2751
     */
11 efrain 2752
    public function test_exceeds_password_length(): void {
1 efrain 2753
        $this->resetAfterTest(true);
2754
 
2755
        // With password less than equals to MAX_PASSWORD_CHARACTERS.
2756
        $this->assertFalse(exceeds_password_length('test'));
2757
 
2758
        // With password more than MAX_PASSWORD_CHARACTERS.
2759
        $password = 'thisisapasswordthatcontainscharactersthatcan';
2760
        $password .= 'exeedthepasswordlengthof128thisispasswordthatcont';
2761
        $password .= 'ainscharactersthatcanexeedthelength-----';
2762
        $this->assertTrue(exceeds_password_length($password));
2763
    }
2764
 
2765
    /**
2766
     * Test function validate_internal_user_password.
2767
     * @covers ::validate_internal_user_password
2768
     */
11 efrain 2769
    public function test_validate_internal_user_password(): void {
1 efrain 2770
        $this->resetAfterTest(true);
2771
        // Test bcrypt hashes (these will be updated but will still count as valid).
2772
        $bcrypthashes = [
2773
            'pw' => '$2y$10$LOSDi5eaQJhutSRun.OVJ.ZSxQZabCMay7TO1KmzMkDMPvU40zGXK',
2774
            'abc' => '$2y$10$VWTOhVdsBbWwtdWNDRHSpewjd3aXBQlBQf5rBY/hVhw8hciarFhXa',
2775
            'C0mP1eX_&}<?@*&%` |\"' => '$2y$10$3PJf.q.9ywNJlsInPbqc8.IFeSsvXrGvQLKRFBIhVu1h1I3vpIry6',
2776
            'ĩńťėŕňăţĩōŋāĹ' => '$2y$10$3A2Y8WpfRAnP3czJiSv6N.6Xp0T8hW3QZz2hUCYhzyWr1kGP1yUve',
2777
        ];
2778
 
2779
        // Test sha512 hashes.
2780
        $sha512hashes = [
2781
            'pw2' => '$6$rounds=10000$0rDIzh/4.MMf9Dm8$Zrj6Ulc1JFj0RFXwMJFsngRSNGlqkPlV1wwRVv7wBLrMeQeMZrwsBO62zy63D//6R5sNGVYQwPB0K8jPCScxB/',
2782
            'abc2' => '$6$rounds=10000$t0L6PklgpijV4tMB$1vpCRKCImsVqTPMiZTi6zLGbs.hpAU8I2BhD/IFliBnHJkFZCWEBfTCq6pEzo0Q8nXsryrgeZ.qngcW.eifuW.',
2783
            'C0mP1eX_&}<?@*&%` |\"2' => '$6$rounds=10000$3TAyVAXN0zmFZ4il$KF8YzduX6Gu0C2xHsY83zoqQ/rLVsb9mLe417wDObo9tO00qeUC68/y2tMq4FL2ixnMPH3OMwzGYo8VJrm8Eq1',
2784
            'ĩńťėŕňăţĩōŋāĹ2' => '$6$rounds=10000$SHR/6ctTkfXOy5NP$YPv42hjDjohVWD3B0boyEYTnLcBXBKO933ijHmkPXNL7BpqAcbYMLfTl9rjsPmCt.1GZvEJZ8ikkCPYBC5Sdp.',
2785
        ];
2786
 
2787
        $validhashes = array_merge($bcrypthashes, $sha512hashes);
2788
 
2789
        foreach ($validhashes as $password => $hash) {
2790
            $user = $this->getDataGenerator()->create_user(array('auth' => 'manual', 'password' => $password));
2791
            $user->password = $hash;
2792
            // The correct password should be validated.
2793
            $this->assertTrue(validate_internal_user_password($user, $password));
2794
            // An incorrect password should not be validated.
2795
            $this->assertFalse(validate_internal_user_password($user, 'badpw'));
2796
        }
2797
    }
2798
 
2799
    /**
2800
     * Test function validate_internal_user_password() with a peppered password,
2801
     * when the pepper no longer exists.
2802
     *
2803
     * @covers ::validate_internal_user_password
2804
     */
11 efrain 2805
    public function test_validate_internal_user_password_bad_pepper(): void {
1 efrain 2806
        global $CFG;
2807
        $this->resetAfterTest();
2808
 
2809
        // Set a pepper.
2810
        $CFG->passwordpeppers = [
2811
                1 => '#GV]NLie|x$H9[$rW%94bXZvJHa%z',
2812
                2 => '#GV]NLie|x$H9[$rW%94bXZvJHa%$'
2813
        ];
2814
        $password = 'test';
2815
 
2816
        $user = $this->getDataGenerator()->create_user(['auth' => 'manual', 'password' => $password]);
2817
        $this->assertTrue(validate_internal_user_password($user, $password));
2818
        $this->assertFalse(validate_internal_user_password($user, 'badpw'));
2819
 
2820
        // Now remove the peppers.
2821
        // Things should not work.
2822
        unset($CFG->passwordpeppers);
2823
        $this->assertFalse(validate_internal_user_password($user, $password));
2824
    }
2825
 
2826
    /**
2827
     * Helper method to test hashing passwords.
2828
     *
2829
     * @param array $passwords
2830
     * @return void
2831
     * @covers ::hash_internal_user_password
2832
     */
2833
    public function validate_hashed_passwords(array $passwords): void {
2834
        foreach ($passwords as $password) {
2835
            $hash = hash_internal_user_password($password);
2836
            $fasthash = hash_internal_user_password($password, true);
2837
            $user = $this->getDataGenerator()->create_user(['auth' => 'manual']);
2838
            $user->password = $hash;
2839
            $this->assertTrue(validate_internal_user_password($user, $password));
2840
 
2841
            // They should not be in bycrypt format.
2842
            $this->assertFalse(password_is_legacy_hash($hash));
2843
 
2844
            // Check that cost factor in hash is correctly set.
2845
            $this->assertMatchesRegularExpression('/\$6\$rounds=10000\$.{103}/', $hash);
2846
            $this->assertMatchesRegularExpression('/\$6\$rounds=5000\$.{103}/', $fasthash);
2847
        }
2848
    }
2849
 
2850
    /**
2851
     * Test function update_internal_user_password.
2852
     * @covers ::update_internal_user_password
2853
     */
11 efrain 2854
    public function test_hash_internal_user_password(): void {
1 efrain 2855
        global $CFG;
2856
        $this->resetAfterTest();
2857
        $passwords = ['pw', 'abc123', 'C0mP1eX_&}<?@*&%` |\"', 'ĩńťėŕňăţĩōŋāĹ'];
2858
 
2859
        // Check that some passwords that we convert to hashes can
2860
        // be validated.
2861
        $this->validate_hashed_passwords($passwords);
2862
 
2863
        // Test again with peppers.
2864
        $CFG->passwordpeppers = [
2865
                1 => '#GV]NLie|x$H9[$rW%94bXZvJHa%z',
2866
                2 => '#GV]NLie|x$H9[$rW%94bXZvJHa%$'
2867
        ];
2868
        $this->validate_hashed_passwords($passwords);
2869
 
2870
        // Add a new pepper and check that things still pass.
2871
        $CFG->passwordpeppers = [
2872
                1 => '#GV]NLie|x$H9[$rW%94bXZvJHa%z',
2873
                2 => '#GV]NLie|x$H9[$rW%94bXZvJHa%$',
2874
                3 => '#GV]NLie|x$H9[$rW%94bXZvJHQ%$'
2875
        ];
2876
        $this->validate_hashed_passwords($passwords);
2877
    }
2878
 
2879
    /**
2880
     * Test function update_internal_user_password().
2881
     */
11 efrain 2882
    public function test_update_internal_user_password(): void {
1 efrain 2883
        global $DB;
2884
        $this->resetAfterTest();
2885
        $passwords = array('password', '1234', 'changeme', '****');
2886
        foreach ($passwords as $password) {
2887
            $user = $this->getDataGenerator()->create_user(array('auth'=>'manual'));
2888
            update_internal_user_password($user, $password);
2889
            // The user object should have been updated.
2890
            $this->assertTrue(validate_internal_user_password($user, $password));
2891
            // The database field for the user should also have been updated to the
2892
            // same value.
2893
            $this->assertSame($user->password, $DB->get_field('user', 'password', array('id' => $user->id)));
2894
        }
2895
 
2896
        $user = $this->getDataGenerator()->create_user(array('auth'=>'manual'));
2897
        // Manually set the user's password to the bcrypt of the string 'password'.
2898
        $DB->set_field('user', 'password', '$2y$10$HhNAYmQcU1GqU/psOmZjfOWlhPEcxx9aEgSJqBfEtYVyq1jPKqMAi', ['id' => $user->id]);
2899
 
2900
        $sink = $this->redirectEvents();
2901
        // Update the password.
2902
        update_internal_user_password($user, 'password');
2903
        $events = $sink->get_events();
2904
        $sink->close();
2905
        $event = array_pop($events);
2906
 
2907
        // Password should have been updated to a SHA512 hash.
2908
        $this->assertFalse(password_is_legacy_hash($user->password));
2909
 
2910
        // Verify event information.
2911
        $this->assertInstanceOf('\core\event\user_password_updated', $event);
2912
        $this->assertSame($user->id, $event->relateduserid);
2913
        $this->assertEquals(\context_user::instance($user->id), $event->get_context());
2914
        $this->assertEventContextNotUsed($event);
2915
 
2916
        // Verify recovery of property 'auth'.
2917
        unset($user->auth);
2918
        update_internal_user_password($user, 'newpassword');
2919
        $this->assertDebuggingCalled('User record in update_internal_user_password() must include field auth',
2920
                DEBUG_DEVELOPER);
2921
        $this->assertEquals('manual', $user->auth);
2922
    }
2923
 
2924
    /**
2925
     * Testing that if the password is not cached, that it does not update
2926
     * the user table and fire event.
11 efrain 2927
     *
2928
     * @dataProvider update_internal_user_password_no_cache_provider
2929
     * @covers ::update_internal_user_password
2930
     *
2931
     * @param string $authmethod The authentication method to set for the user.
2932
     * @param string|null $password The new password to set for the user.
1 efrain 2933
     */
11 efrain 2934
    public function test_update_internal_user_password_no_cache(
2935
        string $authmethod,
2936
        ?string $password,
2937
    ): void {
1 efrain 2938
        global $DB;
2939
        $this->resetAfterTest();
2940
 
11 efrain 2941
        $user = $this->getDataGenerator()->create_user(['auth' => $authmethod]);
1 efrain 2942
        $DB->update_record('user', ['id' => $user->id, 'password' => AUTH_PASSWORD_NOT_CACHED]);
2943
        $user->password = AUTH_PASSWORD_NOT_CACHED;
2944
 
2945
        $sink = $this->redirectEvents();
11 efrain 2946
        update_internal_user_password($user, $password);
1 efrain 2947
        $this->assertEquals(0, $sink->count(), 'User updated event should not fire');
2948
    }
2949
 
2950
    /**
11 efrain 2951
     * The data provider will test the {@see test_update_internal_user_password_no_cache}
2952
     * for accounts using the authentication method with prevent_local_passwords set to true (no cache).
2953
     *
2954
     * @return array
2955
     */
2956
    public static function update_internal_user_password_no_cache_provider(): array {
2957
        return [
1441 ariadna 2958
            'Password is not empty' => ['db', 'wonkawonka'],
11 efrain 2959
            'Password is an empty string' => ['oauth2', ''],
2960
            'Password is null' => ['oauth2', null],
2961
        ];
2962
    }
2963
 
2964
    /**
1 efrain 2965
     * Test if the user has a password hash, but now their auth method
2966
     * says not to cache it.  Then it should update.
2967
     */
11 efrain 2968
    public function test_update_internal_user_password_update_no_cache(): void {
1 efrain 2969
        $this->resetAfterTest();
2970
 
2971
        $user = $this->getDataGenerator()->create_user(array('password' => 'test'));
2972
        $this->assertNotEquals(AUTH_PASSWORD_NOT_CACHED, $user->password);
1441 ariadna 2973
        $user->auth = 'db'; // Change to a auth that does not store passwords.
1 efrain 2974
 
2975
        $sink = $this->redirectEvents();
2976
        update_internal_user_password($user, 'wonkawonka');
2977
        $this->assertGreaterThanOrEqual(1, $sink->count(), 'User updated event should fire');
2978
 
2979
        $this->assertEquals(AUTH_PASSWORD_NOT_CACHED, $user->password);
2980
    }
2981
 
11 efrain 2982
    public function test_fullname(): void {
1 efrain 2983
        global $CFG;
2984
 
2985
        $this->resetAfterTest();
2986
 
2987
        // Create a user to test the name display on.
2988
        $record = array();
2989
        $record['firstname'] = 'Scott';
2990
        $record['lastname'] = 'Fletcher';
2991
        $record['firstnamephonetic'] = 'スコット';
2992
        $record['lastnamephonetic'] = 'フレチャー';
2993
        $record['alternatename'] = 'No friends';
2994
        $user = $this->getDataGenerator()->create_user($record);
2995
 
2996
        // Back up config settings for restore later.
2997
        $originalcfg = new \stdClass();
2998
        $originalcfg->fullnamedisplay = $CFG->fullnamedisplay;
2999
        $originalcfg->alternativefullnameformat = $CFG->alternativefullnameformat;
3000
 
3001
        // Testing existing fullnamedisplay settings.
3002
        $CFG->fullnamedisplay = 'firstname';
3003
        $testname = fullname($user);
3004
        $this->assertSame($user->firstname, $testname);
3005
 
3006
        $CFG->fullnamedisplay = 'firstname lastname';
3007
        $expectedname = "$user->firstname $user->lastname";
3008
        $testname = fullname($user);
3009
        $this->assertSame($expectedname, $testname);
3010
 
3011
        $CFG->fullnamedisplay = 'lastname firstname';
3012
        $expectedname = "$user->lastname $user->firstname";
3013
        $testname = fullname($user);
3014
        $this->assertSame($expectedname, $testname);
3015
 
3016
        $expectedname = get_string('fullnamedisplay', null, $user);
3017
        $CFG->fullnamedisplay = 'language';
3018
        $testname = fullname($user);
3019
        $this->assertSame($expectedname, $testname);
3020
 
3021
        // Test override parameter.
3022
        $CFG->fullnamedisplay = 'firstname';
3023
        $expectedname = "$user->firstname $user->lastname";
3024
        $testname = fullname($user, true);
3025
        $this->assertSame($expectedname, $testname);
3026
 
3027
        // Test alternativefullnameformat setting.
3028
        // Test alternativefullnameformat that has been set to nothing.
3029
        $CFG->alternativefullnameformat = '';
3030
        $expectedname = "$user->firstname $user->lastname";
3031
        $testname = fullname($user, true);
3032
        $this->assertSame($expectedname, $testname);
3033
 
3034
        // Test alternativefullnameformat that has been set to 'language'.
3035
        $CFG->alternativefullnameformat = 'language';
3036
        $expectedname = "$user->firstname $user->lastname";
3037
        $testname = fullname($user, true);
3038
        $this->assertSame($expectedname, $testname);
3039
 
3040
        // Test customising the alternativefullnameformat setting with all additional name fields.
3041
        $CFG->alternativefullnameformat = 'firstname lastname firstnamephonetic lastnamephonetic middlename alternatename';
3042
        $expectedname = "$user->firstname $user->lastname $user->firstnamephonetic $user->lastnamephonetic $user->middlename $user->alternatename";
3043
        $testname = fullname($user, true);
3044
        $this->assertSame($expectedname, $testname);
3045
 
3046
        // Test additional name fields.
3047
        $CFG->fullnamedisplay = 'lastname lastnamephonetic firstname firstnamephonetic';
3048
        $expectedname = "$user->lastname $user->lastnamephonetic $user->firstname $user->firstnamephonetic";
3049
        $testname = fullname($user);
3050
        $this->assertSame($expectedname, $testname);
3051
 
3052
        // Test for handling missing data.
3053
        $user->middlename = null;
3054
        // Parenthesis with no data.
3055
        $CFG->fullnamedisplay = 'firstname (middlename) lastname';
3056
        $expectedname = "$user->firstname $user->lastname";
3057
        $testname = fullname($user);
3058
        $this->assertSame($expectedname, $testname);
3059
 
3060
        // Extra spaces due to no data.
3061
        $CFG->fullnamedisplay = 'firstname middlename lastname';
3062
        $expectedname = "$user->firstname $user->lastname";
3063
        $testname = fullname($user);
3064
        $this->assertSame($expectedname, $testname);
3065
 
3066
        // Regular expression testing.
3067
        // Remove some data from the user fields.
3068
        $user->firstnamephonetic = '';
3069
        $user->lastnamephonetic = '';
3070
 
3071
        // Removing empty brackets and excess whitespace.
3072
        // All of these configurations should resolve to just firstname lastname.
3073
        $configarray = array();
3074
        $configarray[] = 'firstname lastname [firstnamephonetic lastnamephonetic]';
3075
        $configarray[] = 'firstname lastname \'middlename\'';
3076
        $configarray[] = 'firstname "firstnamephonetic" lastname';
3077
        $configarray[] = 'firstname 「firstnamephonetic」 lastname 「lastnamephonetic」';
3078
 
3079
        foreach ($configarray as $config) {
3080
            $CFG->fullnamedisplay = $config;
3081
            $expectedname = "$user->firstname $user->lastname";
3082
            $testname = fullname($user);
3083
            $this->assertSame($expectedname, $testname);
3084
        }
3085
 
3086
        // Check to make sure that other characters are left in place.
3087
        $configarray = array();
3088
        $configarray['0'] = new \stdClass();
3089
        $configarray['0']->config = 'lastname firstname, middlename';
3090
        $configarray['0']->expectedname = "$user->lastname $user->firstname,";
3091
        $configarray['1'] = new \stdClass();
3092
        $configarray['1']->config = 'lastname firstname + alternatename';
3093
        $configarray['1']->expectedname = "$user->lastname $user->firstname + $user->alternatename";
3094
        $configarray['2'] = new \stdClass();
3095
        $configarray['2']->config = 'firstname aka: alternatename';
3096
        $configarray['2']->expectedname = "$user->firstname aka: $user->alternatename";
3097
        $configarray['3'] = new \stdClass();
3098
        $configarray['3']->config = 'firstname (alternatename)';
3099
        $configarray['3']->expectedname = "$user->firstname ($user->alternatename)";
3100
        $configarray['4'] = new \stdClass();
3101
        $configarray['4']->config = 'firstname [alternatename]';
3102
        $configarray['4']->expectedname = "$user->firstname [$user->alternatename]";
3103
        $configarray['5'] = new \stdClass();
3104
        $configarray['5']->config = 'firstname "lastname"';
3105
        $configarray['5']->expectedname = "$user->firstname \"$user->lastname\"";
3106
 
3107
        foreach ($configarray as $config) {
3108
            $CFG->fullnamedisplay = $config->config;
3109
            $expectedname = $config->expectedname;
3110
            $testname = fullname($user);
3111
            $this->assertSame($expectedname, $testname);
3112
        }
3113
 
3114
        // Test debugging message displays when
3115
        // fullnamedisplay setting is "normal".
3116
        $CFG->fullnamedisplay = 'firstname lastname';
3117
        unset($user);
3118
        $user = new \stdClass();
3119
        $user->firstname = 'Stan';
3120
        $user->lastname = 'Lee';
3121
        $namedisplay = fullname($user);
3122
        $this->assertDebuggingCalled();
3123
 
3124
        // Tidy up after we finish testing.
3125
        $CFG->fullnamedisplay = $originalcfg->fullnamedisplay;
3126
        $CFG->alternativefullnameformat = $originalcfg->alternativefullnameformat;
3127
    }
3128
 
11 efrain 3129
    public function test_order_in_string(): void {
1 efrain 3130
        $this->resetAfterTest();
3131
 
3132
        // Return an array in an order as they are encountered in a string.
3133
        $valuearray = array('second', 'firsthalf', 'first');
3134
        $formatstring = 'first firsthalf some other text (second)';
3135
        $expectedarray = array('0' => 'first', '6' => 'firsthalf', '33' => 'second');
3136
        $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3137
 
3138
        // Try again with a different order for the format.
3139
        $valuearray = array('second', 'firsthalf', 'first');
3140
        $formatstring = 'firsthalf first second';
3141
        $expectedarray = array('0' => 'firsthalf', '10' => 'first', '16' => 'second');
3142
        $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3143
 
3144
        // Try again with yet another different order for the format.
3145
        $valuearray = array('second', 'firsthalf', 'first');
3146
        $formatstring = 'start seconds away second firstquater first firsthalf';
3147
        $expectedarray = array('19' => 'second', '38' => 'first', '44' => 'firsthalf');
3148
        $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3149
    }
3150
 
11 efrain 3151
    public function test_complete_user_login(): void {
1 efrain 3152
        global $USER, $DB;
3153
 
3154
        $this->resetAfterTest();
3155
        $user = $this->getDataGenerator()->create_user();
3156
        $this->setUser(0);
3157
 
3158
        $sink = $this->redirectEvents();
3159
        $loginuser = clone($user);
3160
        $this->setCurrentTimeStart();
3161
        @complete_user_login($loginuser); // Hide session header errors.
3162
        $this->assertSame($loginuser, $USER);
3163
        $this->assertEquals($user->id, $USER->id);
3164
        $events = $sink->get_events();
3165
        $sink->close();
3166
 
3167
        $this->assertCount(1, $events);
3168
        $event = reset($events);
3169
        $this->assertInstanceOf('\core\event\user_loggedin', $event);
3170
        $this->assertEquals('user', $event->objecttable);
3171
        $this->assertEquals($user->id, $event->objectid);
3172
        $this->assertEquals(\context_system::instance()->id, $event->contextid);
3173
        $this->assertEventContextNotUsed($event);
3174
 
3175
        $user = $DB->get_record('user', array('id'=>$user->id));
3176
 
3177
        $this->assertTimeCurrent($user->firstaccess);
3178
        $this->assertTimeCurrent($user->lastaccess);
3179
 
3180
        $this->assertTimeCurrent($USER->firstaccess);
3181
        $this->assertTimeCurrent($USER->lastaccess);
3182
        $this->assertTimeCurrent($USER->currentlogin);
3183
        $this->assertSame(sesskey(), $USER->sesskey);
3184
        $this->assertTimeCurrent($USER->preference['_lastloaded']);
3185
        $this->assertObjectNotHasProperty('password', $USER);
3186
        $this->assertObjectNotHasProperty('description', $USER);
3187
    }
3188
 
3189
    /**
3190
     * Test require_logout.
3191
     */
11 efrain 3192
    public function test_require_logout(): void {
1 efrain 3193
        $this->resetAfterTest();
3194
        $user = $this->getDataGenerator()->create_user();
3195
        $this->setUser($user);
3196
 
3197
        $this->assertTrue(isloggedin());
3198
 
3199
        // Logout user and capture event.
3200
        $sink = $this->redirectEvents();
3201
        require_logout();
3202
        $events = $sink->get_events();
3203
        $sink->close();
3204
        $event = array_pop($events);
3205
 
3206
        // Check if user is logged out.
3207
        $this->assertFalse(isloggedin());
3208
 
3209
        // Test Event.
3210
        $this->assertInstanceOf('\core\event\user_loggedout', $event);
3211
        $this->assertSame($user->id, $event->objectid);
3212
        $this->assertEventContextNotUsed($event);
3213
    }
3214
 
3215
    /**
3216
     * A data provider for testing email messageid
3217
     */
1441 ariadna 3218
    public static function generate_email_messageid_provider(): array {
1 efrain 3219
        return array(
3220
            'nopath' => array(
3221
                'wwwroot' => 'http://www.example.com',
1441 ariadna 3222
                'msgids' => array(
1 efrain 3223
                    'a-custom-id' => '<a-custom-id@www.example.com>',
3224
                    'an-id-with-/-a-slash' => '<an-id-with-%2F-a-slash@www.example.com>',
3225
                ),
3226
            ),
3227
            'path' => array(
3228
                'wwwroot' => 'http://www.example.com/path/subdir',
1441 ariadna 3229
                'msgids' => array(
1 efrain 3230
                    'a-custom-id' => '<a-custom-id/path/subdir@www.example.com>',
3231
                    'an-id-with-/-a-slash' => '<an-id-with-%2F-a-slash/path/subdir@www.example.com>',
3232
                ),
3233
            ),
3234
        );
3235
    }
3236
 
3237
    /**
3238
     * Test email message id generation
3239
     *
3240
     * @dataProvider generate_email_messageid_provider
3241
     *
3242
     * @param string $wwwroot The wwwroot
3243
     * @param array $msgids An array of msgid local parts and the final result
3244
     */
11 efrain 3245
    public function test_generate_email_messageid($wwwroot, $msgids): void {
1 efrain 3246
        global $CFG;
3247
 
3248
        $this->resetAfterTest();
3249
        $CFG->wwwroot = $wwwroot;
3250
 
3251
        foreach ($msgids as $local => $final) {
3252
            $this->assertEquals($final, generate_email_messageid($local));
3253
        }
3254
    }
3255
 
3256
    /**
3257
     * Test email with custom headers
3258
     */
11 efrain 3259
    public function test_send_email_with_custom_header(): void {
1 efrain 3260
        global $DB, $CFG;
3261
        $this->preventResetByRollback();
3262
        $this->resetAfterTest();
3263
 
3264
        $touser = $this->getDataGenerator()->create_user();
3265
        $fromuser = $this->getDataGenerator()->create_user();
3266
        $fromuser->customheaders = 'X-Custom-Header: foo';
3267
 
3268
        set_config('allowedemaildomains', 'example.com');
3269
        set_config('emailheaders', 'X-Fixed-Header: bar');
3270
 
3271
        $sink = $this->redirectEmails();
3272
        email_to_user($touser, $fromuser, 'subject', 'message');
3273
 
3274
        $emails = $sink->get_messages();
3275
        $this->assertCount(1, $emails);
3276
        $email = reset($emails);
3277
        $this->assertStringContainsString('X-Custom-Header: foo', $email->header);
3278
        $this->assertStringContainsString("X-Fixed-Header: bar", $email->header);
3279
        $sink->clear();
3280
    }
3281
 
3282
    /**
3283
     * A data provider for testing email diversion
3284
     */
1441 ariadna 3285
    public static function diverted_emails_provider(): array {
1 efrain 3286
        return array(
3287
            'nodiverts' => array(
3288
                'divertallemailsto' => null,
3289
                'divertallemailsexcept' => null,
1441 ariadna 3290
                'addresses' => array(
1 efrain 3291
                    'foo@example.com',
3292
                    'test@real.com',
3293
                    'fred.jones@example.com',
3294
                    'dev1@dev.com',
3295
                    'fred@example.com',
3296
                    'fred+verp@example.com',
3297
                ),
1441 ariadna 3298
                'expected' => false,
1 efrain 3299
            ),
3300
            'alldiverts' => array(
3301
                'divertallemailsto' => 'somewhere@elsewhere.com',
3302
                'divertallemailsexcept' => null,
1441 ariadna 3303
                'addresses' => array(
1 efrain 3304
                    'foo@example.com',
3305
                    'test@real.com',
3306
                    'fred.jones@example.com',
3307
                    'dev1@dev.com',
3308
                    'fred@example.com',
3309
                    'fred+verp@example.com',
3310
                ),
1441 ariadna 3311
                'expected' => true,
1 efrain 3312
            ),
3313
            'alsodiverts' => array(
3314
                'divertallemailsto' => 'somewhere@elsewhere.com',
3315
                'divertallemailsexcept' => '@dev.com, fred(\+.*)?@example.com',
1441 ariadna 3316
                'addresses' => array(
1 efrain 3317
                    'foo@example.com',
3318
                    'test@real.com',
3319
                    'fred.jones@example.com',
3320
                    'Fred.Jones@Example.com',
3321
                ),
1441 ariadna 3322
                'expected' => true,
1 efrain 3323
            ),
3324
            'divertsexceptions' => array(
3325
                'divertallemailsto' => 'somewhere@elsewhere.com',
3326
                'divertallemailsexcept' => '@dev.com, fred(\+.*)?@example.com',
1441 ariadna 3327
                'addresses' => array(
1 efrain 3328
                    'dev1@dev.com',
3329
                    'fred@example.com',
3330
                    'Fred@Example.com',
3331
                    'fred+verp@example.com',
3332
                ),
1441 ariadna 3333
                'expected' => false,
1 efrain 3334
            ),
3335
            'divertsexceptionsnewline' => array(
3336
                'divertallemailsto' => 'somewhere@elsewhere.com',
3337
                'divertallemailsexcept' => "@dev.com\nfred(\+.*)?@example.com",
1441 ariadna 3338
                'addresses' => array(
1 efrain 3339
                    'dev1@dev.com',
3340
                    'fred@example.com',
3341
                    'fred+verp@example.com',
3342
                ),
1441 ariadna 3343
                'expected' => false,
1 efrain 3344
            ),
3345
            'alsodivertsnewline' => array(
3346
                'divertallemailsto' => 'somewhere@elsewhere.com',
3347
                'divertallemailsexcept' => "@dev.com\nfred(\+.*)?@example.com",
1441 ariadna 3348
                'addresses' => array(
1 efrain 3349
                    'foo@example.com',
3350
                    'test@real.com',
3351
                    'fred.jones@example.com',
3352
                ),
1441 ariadna 3353
                'expected' => true,
1 efrain 3354
            ),
3355
            'alsodivertsblankline' => array(
3356
                'divertallemailsto' => 'somewhere@elsewhere.com',
3357
                'divertallemailsexcept' => "@dev.com\n",
1441 ariadna 3358
                'addresses' => [
1 efrain 3359
                    'lionel@example.com',
3360
                ],
1441 ariadna 3361
                'expected' => true,
1 efrain 3362
            ),
3363
            'divertsexceptionblankline' => array(
3364
                'divertallemailsto' => 'somewhere@elsewhere.com',
3365
                'divertallemailsexcept' => "@example.com\n",
1441 ariadna 3366
                'addresses' => [
1 efrain 3367
                    'lionel@example.com',
3368
                ],
1441 ariadna 3369
                'expected' => false,
1 efrain 3370
            ),
3371
        );
3372
    }
3373
 
3374
    /**
3375
     * Test email diversion
3376
     *
3377
     * @dataProvider diverted_emails_provider
3378
     *
3379
     * @param string $divertallemailsto An optional email address
3380
     * @param string $divertallemailsexcept An optional exclusion list
3381
     * @param array $addresses An array of test addresses
3382
     * @param boolean $expected Expected result
3383
     */
11 efrain 3384
    public function test_email_should_be_diverted($divertallemailsto, $divertallemailsexcept, $addresses, $expected): void {
1 efrain 3385
        global $CFG;
3386
 
3387
        $this->resetAfterTest();
3388
        $CFG->divertallemailsto = $divertallemailsto;
3389
        $CFG->divertallemailsexcept = $divertallemailsexcept;
3390
 
3391
        foreach ($addresses as $address) {
3392
            $this->assertEquals($expected, email_should_be_diverted($address));
3393
        }
3394
    }
3395
 
11 efrain 3396
    public function test_email_to_user(): void {
1 efrain 3397
        global $CFG;
3398
 
3399
        $this->resetAfterTest();
3400
 
3401
        $user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1, 'mailformat' => 0));
3402
        $user2 = $this->getDataGenerator()->create_user(array('maildisplay' => 1, 'mailformat' => 1));
3403
        $user3 = $this->getDataGenerator()->create_user(array('maildisplay' => 0));
3404
        set_config('allowedemaildomains', "example.com\r\nmoodle.org");
3405
 
3406
        $subject = 'subject';
3407
        $messagetext = 'message text';
3408
        $subject2 = 'subject 2';
3409
        $messagetext2 = '<b>message text 2</b>';
3410
 
3411
        // Close the default email sink.
3412
        $sink = $this->redirectEmails();
3413
        $sink->close();
3414
 
3415
        $CFG->noemailever = true;
3416
        $this->assertNotEmpty($CFG->noemailever);
3417
        email_to_user($user1, $user2, $subject, $messagetext);
3418
        $this->assertDebuggingCalled('Not sending email due to $CFG->noemailever config setting');
3419
 
3420
        unset_config('noemailever');
3421
 
3422
        email_to_user($user1, $user2, $subject, $messagetext);
3423
        $this->assertDebuggingCalled('Unit tests must not send real emails! Use $this->redirectEmails()');
3424
 
3425
        $sink = $this->redirectEmails();
3426
        email_to_user($user1, $user2, $subject, $messagetext);
3427
        email_to_user($user2, $user1, $subject2, $messagetext2);
3428
        $this->assertSame(2, $sink->count());
3429
        $result = $sink->get_messages();
3430
        $this->assertCount(2, $result);
3431
        $sink->close();
3432
 
3433
        $this->assertSame($subject, $result[0]->subject);
3434
        $this->assertSame($messagetext, trim($result[0]->body));
3435
        $this->assertSame($user1->email, $result[0]->to);
3436
        $this->assertSame($user2->email, $result[0]->from);
3437
        $this->assertStringContainsString('Content-Type: text/plain', $result[0]->header);
3438
 
3439
        $this->assertSame($subject2, $result[1]->subject);
3440
        $this->assertStringContainsString($messagetext2, quoted_printable_decode($result[1]->body));
3441
        $this->assertSame($user2->email, $result[1]->to);
3442
        $this->assertSame($user1->email, $result[1]->from);
3443
        $this->assertStringNotContainsString('Content-Type: text/plain', $result[1]->header);
3444
 
3445
        email_to_user($user1, $user2, $subject, $messagetext);
3446
        $this->assertDebuggingCalled('Unit tests must not send real emails! Use $this->redirectEmails()');
3447
 
3448
        // Test that an empty noreplyaddress will default to a no-reply address.
3449
        $sink = $this->redirectEmails();
3450
        email_to_user($user1, $user3, $subject, $messagetext);
3451
        $result = $sink->get_messages();
3452
        $this->assertEquals($CFG->noreplyaddress, $result[0]->from);
3453
        $sink->close();
3454
        set_config('noreplyaddress', '');
3455
        $sink = $this->redirectEmails();
3456
        email_to_user($user1, $user3, $subject, $messagetext);
3457
        $result = $sink->get_messages();
3458
        $this->assertEquals('noreply@www.example.com', $result[0]->from);
3459
        $sink->close();
3460
 
3461
        // Test $CFG->allowedemaildomains.
3462
        set_config('noreplyaddress', 'noreply@www.example.com');
3463
        $this->assertNotEmpty($CFG->allowedemaildomains);
3464
        $sink = $this->redirectEmails();
3465
        email_to_user($user1, $user2, $subject, $messagetext);
3466
        unset_config('allowedemaildomains');
3467
        email_to_user($user1, $user2, $subject, $messagetext);
3468
        $result = $sink->get_messages();
3469
        $this->assertNotEquals($CFG->noreplyaddress, $result[0]->from);
3470
        $this->assertEquals($CFG->noreplyaddress, $result[1]->from);
3471
        $sink->close();
3472
 
3473
        // Try to send an unsafe attachment, we should see an error message in the eventual mail body.
3474
        $attachment = '../test.txt';
3475
        $attachname = 'txt';
3476
 
3477
        $sink = $this->redirectEmails();
3478
        email_to_user($user1, $user2, $subject, $messagetext, '', $attachment, $attachname);
3479
        $this->assertSame(1, $sink->count());
3480
        $result = $sink->get_messages();
3481
        $this->assertCount(1, $result);
3482
        $this->assertStringContainsString('error.txt', $result[0]->body);
3483
        $this->assertStringContainsString('Error in attachment.  User attempted to attach a filename with a unsafe name.', $result[0]->body);
3484
        $sink->close();
3485
    }
3486
 
3487
    /**
3488
     * Data provider for {@see test_email_to_user_attachment}
3489
     *
3490
     * @return array
3491
     */
1441 ariadna 3492
    public static function email_to_user_attachment_provider(): array {
1 efrain 3493
        global $CFG;
3494
 
3495
        // Return all paths that can be used to send attachments from.
3496
        return [
3497
            'cachedir' => [$CFG->cachedir],
3498
            'dataroot' => [$CFG->dataroot],
3499
            'dirroot' => [$CFG->dirroot],
3500
            'localcachedir' => [$CFG->localcachedir],
3501
            'tempdir' => [$CFG->tempdir],
3502
            // Paths within $CFG->localrequestdir.
3503
            'localrequestdir_request_directory' => [make_request_directory()],
3504
            'localrequestdir_request_storage_directory' => [get_request_storage_directory()],
3505
            // Pass null to indicate we want to test a path relative to $CFG->dataroot.
3506
            'relative' => [null]
3507
        ];
3508
    }
3509
 
3510
    /**
3511
     * Test sending attachments with email_to_user
3512
     *
3513
     * @param string|null $filedir
3514
     *
3515
     * @dataProvider email_to_user_attachment_provider
3516
     */
3517
    public function test_email_to_user_attachment(?string $filedir): void {
3518
        global $CFG;
3519
 
3520
        // If $filedir is null, then write our test file to $CFG->dataroot.
3521
        $filepath = ($filedir ?: $CFG->dataroot) . '/hello.txt';
3522
        file_put_contents($filepath, 'Hello');
3523
 
3524
        $user = \core_user::get_support_user();
3525
        $message = 'Test attachment path';
3526
 
3527
        // Create sink to catch all sent e-mails.
3528
        $sink = $this->redirectEmails();
3529
 
3530
        // Attachment path will be that of the test file if $filedir was passed, otherwise the relative path from $CFG->dataroot.
3531
        $filename = basename($filepath);
3532
        $attachmentpath = $filedir ? $filepath : $filename;
3533
        email_to_user($user, $user, $message, $message, $message, $attachmentpath, $filename);
3534
 
3535
        $messages = $sink->get_messages();
3536
        $sink->close();
3537
 
3538
        $this->assertCount(1, $messages);
3539
 
3540
        // Verify attachment in message body (attachment is in MIME format, but we can detect some Content fields).
3541
        $messagebody = reset($messages)->body;
3542
        $this->assertStringContainsString('Content-Type: text/plain; name=' . $filename, $messagebody);
3543
        $this->assertStringContainsString('Content-Disposition: attachment; filename=' . $filename, $messagebody);
3544
 
3545
        // Cleanup.
3546
        unlink($filepath);
3547
    }
3548
 
3549
    /**
3550
     * Test sending an attachment that doesn't exist to email_to_user
3551
     */
3552
    public function test_email_to_user_attachment_missing(): void {
3553
        $user = \core_user::get_support_user();
3554
        $message = 'Test attachment path';
3555
 
3556
        // Create sink to catch all sent e-mails.
3557
        $sink = $this->redirectEmails();
3558
 
3559
        $attachmentpath = '/hola/hello.txt';
3560
        $filename = basename($attachmentpath);
3561
        email_to_user($user, $user, $message, $message, $message, $attachmentpath, $filename);
3562
 
3563
        $messages = $sink->get_messages();
3564
        $sink->close();
3565
 
3566
        $this->assertCount(1, $messages);
3567
 
3568
        // Verify attachment not in message body (attachment is in MIME format, but we can detect some Content fields).
3569
        $messagebody = reset($messages)->body;
3570
        $this->assertStringNotContainsString('Content-Type: text/plain; name="' . $filename . '"', $messagebody);
3571
        $this->assertStringNotContainsString('Content-Disposition: attachment; filename=' . $filename, $messagebody);
3572
    }
3573
 
3574
    /**
3575
     * Test setnew_password_and_mail.
3576
     */
11 efrain 3577
    public function test_setnew_password_and_mail(): void {
1 efrain 3578
        global $DB, $CFG;
3579
 
3580
        $this->resetAfterTest();
3581
 
3582
        $user = $this->getDataGenerator()->create_user();
3583
 
3584
        // Update user password.
3585
        $sink = $this->redirectEvents();
3586
        $sink2 = $this->redirectEmails(); // Make sure we are redirecting emails.
3587
        setnew_password_and_mail($user);
3588
        $events = $sink->get_events();
3589
        $sink->close();
3590
        $sink2->close();
3591
        $event = array_pop($events);
3592
 
3593
        // Test updated value.
3594
        $dbuser = $DB->get_record('user', array('id' => $user->id));
3595
        $this->assertSame($user->firstname, $dbuser->firstname);
3596
        $this->assertNotEmpty($dbuser->password);
3597
 
3598
        // Test event.
3599
        $this->assertInstanceOf('\core\event\user_password_updated', $event);
3600
        $this->assertSame($user->id, $event->relateduserid);
3601
        $this->assertEquals(\context_user::instance($user->id), $event->get_context());
3602
        $this->assertEventContextNotUsed($event);
3603
    }
3604
 
3605
    /**
3606
     * Data provider for test_generate_confirmation_link
11 efrain 3607
     * @return array Confirmation urls and expected resultant confirmation links
1 efrain 3608
     */
11 efrain 3609
    public static function generate_confirmation_link_provider(): array {
1 efrain 3610
        global $CFG;
3611
        return [
3612
            "Simple name" => [
3613
                "username" => "simplename",
3614
                "confirmationurl" => null,
3615
                "expected" => $CFG->wwwroot . "/login/confirm.php?data=/simplename"
3616
            ],
3617
            "Period in between words in username" => [
3618
                "username" => "period.inbetween",
3619
                "confirmationurl" => null,
3620
                "expected" => $CFG->wwwroot . "/login/confirm.php?data=/period%2Einbetween"
3621
            ],
3622
            "Trailing periods in username" => [
3623
                "username" => "trailingperiods...",
3624
                "confirmationurl" => null,
3625
                "expected" => $CFG->wwwroot . "/login/confirm.php?data=/trailingperiods%2E%2E%2E"
3626
            ],
3627
            "At symbol in username" => [
3628
                "username" => "at@symbol",
3629
                "confirmationurl" => null,
3630
                "expected" => $CFG->wwwroot . "/login/confirm.php?data=/at%40symbol"
3631
            ],
3632
            "Dash symbol in username" => [
3633
                "username" => "has-dash",
3634
                "confirmationurl" => null,
3635
                "expected" => $CFG->wwwroot . "/login/confirm.php?data=/has-dash"
3636
            ],
3637
            "Underscore in username" => [
3638
                "username" => "under_score",
3639
                "confirmationurl" => null,
3640
                "expected" => $CFG->wwwroot . "/login/confirm.php?data=/under_score"
3641
            ],
3642
            "Many different characters in username" => [
3643
                "username" => "many_-.@characters@_@-..-..",
3644
                "confirmationurl" => null,
3645
                "expected" => $CFG->wwwroot . "/login/confirm.php?data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3646
            ],
3647
            "Custom relative confirmation url" => [
3648
                "username" => "many_-.@characters@_@-..-..",
3649
                "confirmationurl" => "/custom/local/url.php",
3650
                "expected" => $CFG->wwwroot . "/custom/local/url.php?data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3651
            ],
3652
            "Custom relative confirmation url with parameters" => [
3653
                "username" => "many_-.@characters@_@-..-..",
3654
                "confirmationurl" => "/custom/local/url.php?with=param",
3655
                "expected" => $CFG->wwwroot . "/custom/local/url.php?with=param&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3656
            ],
3657
            "Custom local confirmation url" => [
3658
                "username" => "many_-.@characters@_@-..-..",
3659
                "confirmationurl" => $CFG->wwwroot . "/custom/local/url.php",
3660
                "expected" => $CFG->wwwroot . "/custom/local/url.php?data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3661
            ],
3662
            "Custom local confirmation url with parameters" => [
3663
                "username" => "many_-.@characters@_@-..-..",
3664
                "confirmationurl" => $CFG->wwwroot . "/custom/local/url.php?with=param",
3665
                "expected" => $CFG->wwwroot . "/custom/local/url.php?with=param&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3666
            ],
3667
            "Custom external confirmation url" => [
3668
                "username" => "many_-.@characters@_@-..-..",
3669
                "confirmationurl" => "http://moodle.org/custom/external/url.php",
3670
                "expected" => "http://moodle.org/custom/external/url.php?data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3671
            ],
3672
            "Custom external confirmation url with parameters" => [
3673
                "username" => "many_-.@characters@_@-..-..",
3674
                "confirmationurl" => "http://moodle.org/ext.php?with=some&param=eters",
3675
                "expected" => "http://moodle.org/ext.php?with=some&param=eters&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3676
            ],
11 efrain 3677
            "Custom external confirmation url with parameters (again)" => [
1 efrain 3678
                "username" => "many_-.@characters@_@-..-..",
3679
                "confirmationurl" => "http://moodle.org/ext.php?with=some&data=test",
3680
                "expected" => "http://moodle.org/ext.php?with=some&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3681
            ],
3682
        ];
3683
    }
3684
 
3685
    /**
3686
     * Test generate_confirmation_link
3687
     * @dataProvider generate_confirmation_link_provider
3688
     * @param string $username The name of the user
3689
     * @param string $confirmationurl The url the user should go to to confirm
3690
     * @param string $expected The expected url of the confirmation link
3691
     */
11 efrain 3692
    public function test_generate_confirmation_link($username, $confirmationurl, $expected): void {
1 efrain 3693
        $this->resetAfterTest();
3694
        $sink = $this->redirectEmails();
3695
 
3696
        $user = $this->getDataGenerator()->create_user(
3697
            [
3698
                "username" => $username,
3699
                "confirmed" => 0,
3700
                "email" => 'test@example.com',
3701
            ]
3702
        );
3703
 
3704
        send_confirmation_email($user, $confirmationurl);
3705
        $sink->close();
3706
        $messages = $sink->get_messages();
3707
        $message = array_shift($messages);
3708
        $messagebody = quoted_printable_decode($message->body);
3709
 
3710
        $this->assertStringContainsString($expected, $messagebody);
3711
    }
3712
 
3713
    /**
3714
     * Test generate_confirmation_link with custom admin link
3715
     */
11 efrain 3716
    public function test_generate_confirmation_link_with_custom_admin(): void {
1 efrain 3717
        global $CFG;
3718
 
3719
        $this->resetAfterTest();
3720
        $sink = $this->redirectEmails();
3721
 
3722
        $admin = $CFG->admin;
3723
        $CFG->admin = 'custom/admin/path';
3724
 
3725
        $user = $this->getDataGenerator()->create_user(
3726
            [
3727
                "username" => "many_-.@characters@_@-..-..",
3728
                "confirmed" => 0,
3729
                "email" => 'test@example.com',
3730
            ]
3731
        );
3732
        $confirmationurl = "/admin/test.php?with=params";
3733
        $expected = $CFG->wwwroot . "/" . $CFG->admin . "/test.php?with=params&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E";
3734
 
3735
        send_confirmation_email($user, $confirmationurl);
3736
        $sink->close();
3737
        $messages = $sink->get_messages();
3738
        $message = array_shift($messages);
3739
        $messagebody = quoted_printable_decode($message->body);
3740
 
3741
        $sink->close();
3742
        $this->assertStringContainsString($expected, $messagebody);
3743
 
3744
        $CFG->admin = $admin;
3745
    }
3746
 
3747
 
3748
    /**
3749
     * Test remove_course_content deletes course contents
3750
     * TODO Add asserts to verify other data related to course is deleted as well.
3751
     */
11 efrain 3752
    public function test_remove_course_contents(): void {
1 efrain 3753
 
3754
        $this->resetAfterTest();
3755
 
3756
        $course = $this->getDataGenerator()->create_course();
3757
        $user = $this->getDataGenerator()->create_user();
3758
        $gen = $this->getDataGenerator()->get_plugin_generator('core_notes');
3759
        $note = $gen->create_instance(array('courseid' => $course->id, 'userid' => $user->id));
3760
 
3761
        $this->assertNotEquals(false, note_load($note->id));
3762
        remove_course_contents($course->id, false);
3763
        $this->assertFalse(note_load($note->id));
3764
    }
3765
 
3766
    /**
3767
     * Test function username_load_fields_from_object().
3768
     */
11 efrain 3769
    public function test_username_load_fields_from_object(): void {
1 efrain 3770
        $this->resetAfterTest();
3771
 
3772
        // This object represents the information returned from an sql query.
3773
        $userinfo = new \stdClass();
3774
        $userinfo->userid = 1;
3775
        $userinfo->username = 'loosebruce';
3776
        $userinfo->firstname = 'Bruce';
3777
        $userinfo->lastname = 'Campbell';
3778
        $userinfo->firstnamephonetic = 'ブルース';
3779
        $userinfo->lastnamephonetic = 'カンベッル';
3780
        $userinfo->middlename = '';
3781
        $userinfo->alternatename = '';
3782
        $userinfo->email = '';
3783
        $userinfo->picture = 23;
3784
        $userinfo->imagealt = 'Michael Jordan draining another basket.';
3785
        $userinfo->idnumber = 3982;
3786
 
3787
        // Just user name fields.
3788
        $user = new \stdClass();
3789
        $user = username_load_fields_from_object($user, $userinfo);
3790
        $expectedarray = new \stdClass();
3791
        $expectedarray->firstname = 'Bruce';
3792
        $expectedarray->lastname = 'Campbell';
3793
        $expectedarray->firstnamephonetic = 'ブルース';
3794
        $expectedarray->lastnamephonetic = 'カンベッル';
3795
        $expectedarray->middlename = '';
3796
        $expectedarray->alternatename = '';
3797
        $this->assertEquals($user, $expectedarray);
3798
 
3799
        // User information for showing a picture.
3800
        $user = new \stdClass();
3801
        $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
3802
        $user = username_load_fields_from_object($user, $userinfo, null, $additionalfields);
3803
        $user->id = $userinfo->userid;
3804
        $expectedarray = new \stdClass();
3805
        $expectedarray->id = 1;
3806
        $expectedarray->firstname = 'Bruce';
3807
        $expectedarray->lastname = 'Campbell';
3808
        $expectedarray->firstnamephonetic = 'ブルース';
3809
        $expectedarray->lastnamephonetic = 'カンベッル';
3810
        $expectedarray->middlename = '';
3811
        $expectedarray->alternatename = '';
3812
        $expectedarray->email = '';
3813
        $expectedarray->picture = 23;
3814
        $expectedarray->imagealt = 'Michael Jordan draining another basket.';
3815
        $this->assertEquals($user, $expectedarray);
3816
 
3817
        // Alter the userinfo object to have a prefix.
3818
        $userinfo->authorfirstname = 'Bruce';
3819
        $userinfo->authorlastname = 'Campbell';
3820
        $userinfo->authorfirstnamephonetic = 'ブルース';
3821
        $userinfo->authorlastnamephonetic = 'カンベッル';
3822
        $userinfo->authormiddlename = '';
3823
        $userinfo->authorpicture = 23;
3824
        $userinfo->authorimagealt = 'Michael Jordan draining another basket.';
3825
        $userinfo->authoremail = 'test@example.com';
3826
 
3827
 
3828
        // Return an object with user picture information.
3829
        $user = new \stdClass();
3830
        $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
3831
        $user = username_load_fields_from_object($user, $userinfo, 'author', $additionalfields);
3832
        $user->id = $userinfo->userid;
3833
        $expectedarray = new \stdClass();
3834
        $expectedarray->id = 1;
3835
        $expectedarray->firstname = 'Bruce';
3836
        $expectedarray->lastname = 'Campbell';
3837
        $expectedarray->firstnamephonetic = 'ブルース';
3838
        $expectedarray->lastnamephonetic = 'カンベッル';
3839
        $expectedarray->middlename = '';
3840
        $expectedarray->alternatename = '';
3841
        $expectedarray->email = 'test@example.com';
3842
        $expectedarray->picture = 23;
3843
        $expectedarray->imagealt = 'Michael Jordan draining another basket.';
3844
        $this->assertEquals($user, $expectedarray);
3845
    }
3846
 
3847
    /**
3848
     * Test function {@see count_words()}.
3849
     *
3850
     * @dataProvider count_words_testcases
3851
     * @param int $expectedcount number of words in $string.
3852
     * @param string $string the test string to count the words of.
3853
     * @param int|null $format
3854
     */
3855
    public function test_count_words(int $expectedcount, string $string, $format = null): void {
3856
        $this->assertEquals($expectedcount, count_words($string, $format),
3857
            "'$string' with format '$format' does not match count $expectedcount");
3858
    }
3859
 
3860
    /**
3861
     * Data provider for {@see test_count_words}.
3862
     *
3863
     * @return array of test cases.
3864
     */
1441 ariadna 3865
    public static function count_words_testcases(): array {
1 efrain 3866
        // Copy-pasting example from MDL-64240.
3867
        $copypasted = <<<EOT
3868
<p onclick="alert('boop');">Snoot is booped</p>
3869
 <script>alert('Boop the snoot');</script>
3870
 <img alt="Boop the Snoot." src="https://proxy.duckduckgo.com/iu/?u=http%3A%2F%2Fwww.geekfill.com%2Fwp-content%2Fuploads%2F2015%2F08%2FBoop-the-Snoot.jpg&f=1">
3871
EOT;
3872
 
3873
        // The counts here should match MS Word and Libre Office.
3874
        return [
3875
            [0, ''],
3876
            [4, 'one two three four'],
3877
            [1, "a'b"],
3878
            [1, '1+1=2'],
3879
            [1, ' one-sided '],
3880
            [2, 'one&nbsp;two'],
3881
            [1, 'email@example.com'],
3882
            [2, 'first\part second/part'],
3883
            [4, '<p>one two<br></br>three four</p>'],
3884
            [4, '<p>one two<br>three four</p>'],
3885
            [4, '<p>one two<br />three four</p>'], // XHTML style.
3886
            [3, ' one ... three '],
3887
            [1, 'just...one'],
3888
            [3, ' one & three '],
3889
            [1, 'just&one'],
3890
            [2, 'em—dash'],
3891
            [2, 'en–dash'],
3892
            [4, '1³ £2 €3.45 $6,789'],
3893
            [2, 'ブルース カンベッル'], // MS word counts this as 11, but we don't handle that yet.
3894
            [4, '<p>one two</p><p>three four</p>'],
3895
            [4, '<p>one two</p><p><br/></p><p>three four</p>'],
3896
            [4, '<p>one</p><ul><li>two</li><li>three</li></ul><p>four.</p>'],
3897
            [1, '<p>em<b>phas</b>is.</p>'],
3898
            [1, '<p>em<i>phas</i>is.</p>'],
3899
            [1, '<p>em<strong>phas</strong>is.</p>'],
3900
            [1, '<p>em<em>phas</em>is.</p>'],
3901
            [2, "one\ntwo"],
3902
            [2, "one\rtwo"],
3903
            [2, "one\ttwo"],
3904
            [2, "one\vtwo"],
3905
            [2, "one\ftwo"],
3906
            [1, "SO<sub>4</sub><sup>2-</sup>"],
3907
            [6, '4+4=8 i.e. O(1) a,b,c,d I’m black&blue_really'],
3908
            [1, '<span>a</span><span>b</span>'],
3909
            [1, '<span>a</span><span>b</span>', FORMAT_PLAIN],
3910
            [1, '<span>a</span><span>b</span>', FORMAT_HTML],
3911
            [1, '<span>a</span><span>b</span>', FORMAT_MOODLE],
3912
            [1, '<span>a</span><span>b</span>', FORMAT_MARKDOWN],
3913
            [1, 'aa <argh <bleh>pokus</bleh>'],
3914
            [2, 'aa <argh <bleh>pokus</bleh>', FORMAT_HTML],
3915
            [6, $copypasted],
3916
            [6, $copypasted, FORMAT_PLAIN],
3917
            [3, $copypasted, FORMAT_HTML],
3918
            [3, $copypasted, FORMAT_MOODLE],
3919
        ];
3920
    }
3921
 
3922
    /**
3923
     * Test function {@see count_letters()}.
3924
     *
3925
     * @dataProvider count_letters_testcases
3926
     * @param int $expectedcount number of characters in $string.
3927
     * @param string $string the test string to count the letters of.
3928
     * @param int|null $format
3929
     */
3930
    public function test_count_letters(int $expectedcount, string $string, $format = null): void {
3931
        $this->assertEquals($expectedcount, count_letters($string, $format),
3932
            "'$string' with format '$format' does not match count $expectedcount");
3933
    }
3934
 
3935
    /**
3936
     * Data provider for {@see count_letters_testcases}.
3937
     *
3938
     * @return array of test cases.
3939
     */
1441 ariadna 3940
    public static function count_letters_testcases(): array {
1 efrain 3941
        return [
3942
            [0, ''],
3943
            [1, 'x'],
3944
            [1, '&amp;'],
3945
            [4, '<p>frog</p>'],
3946
            [4, '<p>frog</p>', FORMAT_PLAIN],
3947
            [4, '<p>frog</p>', FORMAT_MOODLE],
3948
            [4, '<p>frog</p>', FORMAT_HTML],
3949
            [4, '<p>frog</p>', FORMAT_MARKDOWN],
3950
            [2, 'aa <argh <bleh>pokus</bleh>'],
3951
            [7, 'aa <argh <bleh>pokus</bleh>', FORMAT_HTML],
3952
        ];
3953
    }
3954
 
3955
    /**
3956
     * Tests the getremoteaddr() function.
3957
     */
11 efrain 3958
    public function test_getremoteaddr(): void {
1 efrain 3959
        global $CFG;
3960
 
3961
        $this->resetAfterTest();
3962
 
3963
        $CFG->getremoteaddrconf = null; // Use default value, GETREMOTEADDR_SKIP_DEFAULT.
3964
        $noip = getremoteaddr('1.1.1.1');
3965
        $this->assertEquals('1.1.1.1', $noip);
3966
 
3967
        $remoteaddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
3968
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
3969
        $singleip = getremoteaddr();
3970
        $this->assertEquals('127.0.0.1', $singleip);
3971
 
3972
        $_SERVER['REMOTE_ADDR'] = $remoteaddr; // Restore server value.
3973
 
3974
        $CFG->getremoteaddrconf = 0; // Don't skip any source.
3975
        $noip = getremoteaddr('1.1.1.1');
3976
        $this->assertEquals('1.1.1.1', $noip);
3977
 
3978
        // Populate all $_SERVER values to review order.
3979
        $ipsources = [
3980
            'HTTP_CLIENT_IP' => '2.2.2.2',
3981
            'HTTP_X_FORWARDED_FOR' => '3.3.3.3',
3982
            'REMOTE_ADDR' => '4.4.4.4',
3983
        ];
3984
        $originalvalues = [];
3985
        foreach ($ipsources as $source => $ip) {
3986
            $originalvalues[$source] = isset($_SERVER[$source]) ? $_SERVER[$source] : null; // Saving data to restore later.
3987
            $_SERVER[$source] = $ip;
3988
        }
3989
 
3990
        foreach ($ipsources as $source => $expectedip) {
3991
            $ip = getremoteaddr();
3992
            $this->assertEquals($expectedip, $ip);
3993
            unset($_SERVER[$source]); // Removing the value so next time we get the following ip.
3994
        }
3995
 
3996
        // Restore server values.
3997
        foreach ($originalvalues as $source => $ip) {
3998
            $_SERVER[$source] = $ip;
3999
        }
4000
 
4001
        // All $_SERVER values have been removed, we should get the default again.
4002
        $noip = getremoteaddr('1.1.1.1');
4003
        $this->assertEquals('1.1.1.1', $noip);
4004
 
4005
        $CFG->getremoteaddrconf = GETREMOTEADDR_SKIP_HTTP_CLIENT_IP;
4006
        $xforwardedfor = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : null;
4007
 
4008
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '';
4009
        $noip = getremoteaddr('1.1.1.1');
4010
        $this->assertEquals('1.1.1.1', $noip);
4011
 
4012
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '';
4013
        $noip = getremoteaddr();
4014
        $this->assertEquals('0.0.0.0', $noip);
4015
 
4016
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1';
4017
        $singleip = getremoteaddr();
4018
        $this->assertEquals('127.0.0.1', $singleip);
4019
 
4020
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2';
4021
        $twoip = getremoteaddr();
4022
        $this->assertEquals('127.0.0.2', $twoip);
4023
 
4024
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2,127.0.0.3';
4025
        $threeip = getremoteaddr();
4026
        $this->assertEquals('127.0.0.3', $threeip);
4027
 
4028
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2:65535';
4029
        $portip = getremoteaddr();
4030
        $this->assertEquals('127.0.0.2', $portip);
4031
 
4032
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,0:0:0:0:0:0:0:2';
4033
        $portip = getremoteaddr();
4034
        $this->assertEquals('0:0:0:0:0:0:0:2', $portip);
4035
 
4036
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,0::2';
4037
        $portip = getremoteaddr();
4038
        $this->assertEquals('0:0:0:0:0:0:0:2', $portip);
4039
 
4040
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,[0:0:0:0:0:0:0:2]:65535';
4041
        $portip = getremoteaddr();
4042
        $this->assertEquals('0:0:0:0:0:0:0:2', $portip);
4043
 
4044
        $_SERVER['HTTP_X_FORWARDED_FOR'] = $xforwardedfor;
4045
 
4046
    }
4047
 
4048
    /**
4049
     * Test function for creation of random strings.
4050
     */
11 efrain 4051
    public function test_random_string(): void {
1 efrain 4052
        $pool = 'a-zA-Z0-9';
4053
 
4054
        $result = random_string(10);
4055
        $this->assertSame(10, strlen($result));
4056
        $this->assertMatchesRegularExpression('/^[' . $pool . ']+$/', $result);
4057
        $this->assertNotSame($result, random_string(10));
4058
 
4059
        $result = random_string(21);
4060
        $this->assertSame(21, strlen($result));
4061
        $this->assertMatchesRegularExpression('/^[' . $pool . ']+$/', $result);
4062
        $this->assertNotSame($result, random_string(21));
4063
 
4064
        $result = random_string(666);
4065
        $this->assertSame(666, strlen($result));
4066
        $this->assertMatchesRegularExpression('/^[' . $pool . ']+$/', $result);
4067
 
4068
        $result = random_string();
4069
        $this->assertSame(15, strlen($result));
4070
        $this->assertMatchesRegularExpression('/^[' . $pool . ']+$/', $result);
4071
 
4072
        $this->assertDebuggingNotCalled();
4073
    }
4074
 
4075
    /**
4076
     * Test function for creation of complex random strings.
4077
     */
11 efrain 4078
    public function test_complex_random_string(): void {
1 efrain 4079
        $pool = preg_quote('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`~!@#%^&*()_+-=[];,./<>?:{} ', '/');
4080
 
4081
        $result = complex_random_string(10);
4082
        $this->assertSame(10, strlen($result));
4083
        $this->assertMatchesRegularExpression('/^[' . $pool . ']+$/', $result);
4084
        $this->assertNotSame($result, complex_random_string(10));
4085
 
4086
        $result = complex_random_string(21);
4087
        $this->assertSame(21, strlen($result));
4088
        $this->assertMatchesRegularExpression('/^[' . $pool . ']+$/', $result);
4089
        $this->assertNotSame($result, complex_random_string(21));
4090
 
4091
        $result = complex_random_string(666);
4092
        $this->assertSame(666, strlen($result));
4093
        $this->assertMatchesRegularExpression('/^[' . $pool . ']+$/', $result);
4094
 
4095
        $result = complex_random_string();
4096
        $this->assertEqualsWithDelta(28, strlen($result), 4); // Expected length is 24 - 32.
4097
        $this->assertMatchesRegularExpression('/^[' . $pool . ']+$/', $result);
4098
 
4099
        $this->assertDebuggingNotCalled();
4100
    }
4101
 
4102
    /**
4103
     * Data provider for private ips.
4104
     */
1441 ariadna 4105
    public static function data_private_ips(): array {
1 efrain 4106
        return array(
4107
            array('10.0.0.0'),
4108
            array('172.16.0.0'),
4109
            array('192.168.1.0'),
4110
            array('fdfe:dcba:9876:ffff:fdc6:c46b:bb8f:7d4c'),
4111
            array('fdc6:c46b:bb8f:7d4c:fdc6:c46b:bb8f:7d4c'),
4112
            array('fdc6:c46b:bb8f:7d4c:0000:8a2e:0370:7334'),
4113
            array('127.0.0.1'), // This has been buggy in past: https://bugs.php.net/bug.php?id=53150.
4114
        );
4115
    }
4116
 
4117
    /**
4118
     * Checks ip_is_public returns false for private ips.
4119
     *
4120
     * @param string $ip the ipaddress to test
4121
     * @dataProvider data_private_ips
4122
     */
11 efrain 4123
    public function test_ip_is_public_private_ips($ip): void {
1 efrain 4124
        $this->assertFalse(ip_is_public($ip));
4125
    }
4126
 
4127
    /**
4128
     * Data provider for public ips.
4129
     */
1441 ariadna 4130
    public static function data_public_ips(): array {
1 efrain 4131
        return array(
4132
            array('2400:cb00:2048:1::8d65:71b3'),
4133
            array('2400:6180:0:d0::1b:2001'),
4134
            array('141.101.113.179'),
4135
            array('123.45.67.178'),
4136
        );
4137
    }
4138
 
4139
    /**
4140
     * Checks ip_is_public returns true for public ips.
4141
     *
4142
     * @param string $ip the ipaddress to test
4143
     * @dataProvider data_public_ips
4144
     */
11 efrain 4145
    public function test_ip_is_public_public_ips($ip): void {
1 efrain 4146
        $this->assertTrue(ip_is_public($ip));
4147
    }
4148
 
4149
    /**
4150
     * Test the function can_send_from_real_email_address
4151
     *
4152
     * @param string $email Email address for the from user.
4153
     * @param int $display The user's email display preference.
4154
     * @param bool $samecourse Are the users in the same course?
4155
     * @param string $config The CFG->allowedemaildomains config values
4156
     * @param bool $result The expected result.
4157
     * @dataProvider data_can_send_from_real_email_address
4158
     */
11 efrain 4159
    public function test_can_send_from_real_email_address($email, $display, $samecourse, $config, $result): void {
1 efrain 4160
        $this->resetAfterTest();
4161
 
4162
        $fromuser = $this->getDataGenerator()->create_user();
4163
        $touser = $this->getDataGenerator()->create_user();
4164
        $course = $this->getDataGenerator()->create_course();
4165
        set_config('allowedemaildomains', $config);
4166
 
4167
        $fromuser->email = $email;
4168
        $fromuser->maildisplay = $display;
4169
        if ($samecourse) {
4170
            $this->getDataGenerator()->enrol_user($fromuser->id, $course->id, 'student');
4171
            $this->getDataGenerator()->enrol_user($touser->id, $course->id, 'student');
4172
        } else {
4173
            $this->getDataGenerator()->enrol_user($fromuser->id, $course->id, 'student');
4174
        }
4175
        $this->assertEquals($result, can_send_from_real_email_address($fromuser, $touser));
4176
    }
4177
 
4178
    /**
4179
     * Data provider for test_can_send_from_real_email_address.
4180
     *
4181
     * @return array Returns an array of test data for the above function.
4182
     */
1441 ariadna 4183
    public static function data_can_send_from_real_email_address(): array {
1 efrain 4184
        return [
4185
            // Test from email is in allowed domain.
4186
            // Test that from display is set to show no one.
4187
            [
4188
                'email' => 'fromuser@example.com',
4189
                'display' => \core_user::MAILDISPLAY_HIDE,
4190
                'samecourse' => false,
4191
                'config' => "example.com\r\ntest.com",
4192
                'result' => false
4193
            ],
4194
            // Test that from display is set to course members only (course member).
4195
            [
4196
                'email' => 'fromuser@example.com',
4197
                'display' => \core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY,
4198
                'samecourse' => true,
4199
                'config' => "example.com\r\ntest.com",
4200
                'result' => true
4201
            ],
4202
            // Test that from display is set to course members only (Non course member).
4203
            [
4204
                'email' => 'fromuser@example.com',
4205
                'display' => \core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY,
4206
                'samecourse' => false,
4207
                'config' => "example.com\r\ntest.com",
4208
                'result' => false
4209
            ],
4210
            // Test that from display is set to show everyone.
4211
            [
4212
                'email' => 'fromuser@example.com',
4213
                'display' => \core_user::MAILDISPLAY_EVERYONE,
4214
                'samecourse' => false,
4215
                'config' => "example.com\r\ntest.com",
4216
                'result' => true
4217
            ],
4218
            // Test a few different config value formats for parsing correctness.
4219
            [
4220
                'email' => 'fromuser@example.com',
4221
                'display' => \core_user::MAILDISPLAY_EVERYONE,
4222
                'samecourse' => false,
4223
                'config' => "\n test.com\nexample.com \n",
4224
                'result' => true
4225
            ],
4226
            [
4227
                'email' => 'fromuser@example.com',
4228
                'display' => \core_user::MAILDISPLAY_EVERYONE,
4229
                'samecourse' => false,
4230
                'config' => "\r\n example.com \r\n test.com \r\n",
4231
                'result' => true
4232
            ],
4233
            [
4234
                'email' => 'fromuser@EXAMPLE.com',
4235
                'display' => \core_user::MAILDISPLAY_EVERYONE,
4236
                'samecourse' => false,
4237
                'config' => "example.com\r\ntest.com",
4238
                'result' => true,
4239
            ],
4240
            // Test from email is not in allowed domain.
4241
            // Test that from display is set to show no one.
4242
            [   'email' => 'fromuser@moodle.com',
4243
                'display' => \core_user::MAILDISPLAY_HIDE,
4244
                'samecourse' => false,
4245
                'config' => "example.com\r\ntest.com",
4246
                'result' => false
4247
            ],
4248
            // Test that from display is set to course members only (course member).
4249
            [   'email' => 'fromuser@moodle.com',
4250
                'display' => \core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY,
4251
                'samecourse' => true,
4252
                'config' => "example.com\r\ntest.com",
4253
                'result' => false
4254
            ],
4255
            // Test that from display is set to course members only (Non course member.
4256
            [   'email' => 'fromuser@moodle.com',
4257
                'display' => \core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY,
4258
                'samecourse' => false,
4259
                'config' => "example.com\r\ntest.com",
4260
                'result' => false
4261
            ],
4262
            // Test that from display is set to show everyone.
4263
            [   'email' => 'fromuser@moodle.com',
4264
                'display' => \core_user::MAILDISPLAY_EVERYONE,
4265
                'samecourse' => false,
4266
                'config' => "example.com\r\ntest.com",
4267
                'result' => false
4268
            ],
4269
            // Test a few erroneous config value and confirm failure.
4270
            [   'email' => 'fromuser@moodle.com',
4271
                'display' => \core_user::MAILDISPLAY_EVERYONE,
4272
                'samecourse' => false,
4273
                'config' => "\r\n   \r\n",
4274
                'result' => false
4275
            ],
4276
            [   'email' => 'fromuser@moodle.com',
4277
                'display' => \core_user::MAILDISPLAY_EVERYONE,
4278
                'samecourse' => false,
4279
                'config' => " \n   \n \n ",
4280
                'result' => false
4281
            ],
4282
        ];
4283
    }
4284
 
4285
    /**
4286
     * Test that generate_email_processing_address() returns valid email address.
4287
     */
11 efrain 4288
    public function test_generate_email_processing_address(): void {
1 efrain 4289
        global $CFG;
4290
        $this->resetAfterTest();
4291
 
4292
        $data = (object)[
4293
            'id' => 42,
4294
            'email' => 'my.email+from_moodle@example.com',
4295
        ];
4296
 
4297
        $modargs = 'B'.base64_encode(pack('V', $data->id)).substr(md5($data->email), 0, 16);
4298
 
4299
        $CFG->maildomain = 'example.com';
4300
        $CFG->mailprefix = 'mdl+';
4301
        $this->assertTrue(validate_email(generate_email_processing_address(0, $modargs)));
4302
 
4303
        $CFG->maildomain = 'mail.example.com';
4304
        $CFG->mailprefix = 'mdl-';
4305
        $this->assertTrue(validate_email(generate_email_processing_address(23, $modargs)));
4306
    }
4307
 
4308
    /**
4309
     * Test allowemailaddresses setting.
4310
     *
4311
     * @param string $email Email address for the from user.
4312
     * @param string $config The CFG->allowemailaddresses config values
4313
     * @param false/string $result The expected result.
4314
     *
4315
     * @dataProvider data_email_is_not_allowed_for_allowemailaddresses
4316
     */
11 efrain 4317
    public function test_email_is_not_allowed_for_allowemailaddresses($email, $config, $result): void {
1 efrain 4318
        $this->resetAfterTest();
4319
 
4320
        set_config('allowemailaddresses', $config);
4321
        $this->assertEquals($result, email_is_not_allowed($email));
4322
    }
4323
 
4324
    /**
4325
     * Data provider for data_email_is_not_allowed_for_allowemailaddresses.
4326
     *
4327
     * @return array Returns an array of test data for the above function.
4328
     */
1441 ariadna 4329
    public static function data_email_is_not_allowed_for_allowemailaddresses(): array {
1 efrain 4330
        return [
4331
            // Test allowed domain empty list.
4332
            [
4333
                'email' => 'fromuser@example.com',
4334
                'config' => '',
4335
                'result' => false
4336
            ],
4337
            // Test from email is in allowed domain.
4338
            [
4339
                'email' => 'fromuser@example.com',
4340
                'config' => 'example.com test.com',
4341
                'result' => false
4342
            ],
4343
            // Test from email is in allowed domain but uppercase config.
4344
            [
4345
                'email' => 'fromuser@example.com',
4346
                'config' => 'EXAMPLE.com test.com',
4347
                'result' => false
4348
            ],
4349
            // Test from email is in allowed domain but uppercase email.
4350
            [
4351
                'email' => 'fromuser@EXAMPLE.com',
4352
                'config' => 'example.com test.com',
4353
                'result' => false
4354
            ],
4355
            // Test from email is in allowed subdomain.
4356
            [
4357
                'email' => 'fromuser@something.example.com',
4358
                'config' => '.example.com test.com',
4359
                'result' => false
4360
            ],
4361
            // Test from email is in allowed subdomain but uppercase config.
4362
            [
4363
                'email' => 'fromuser@something.example.com',
4364
                'config' => '.EXAMPLE.com test.com',
4365
                'result' => false
4366
            ],
4367
            // Test from email is in allowed subdomain but uppercase email.
4368
            [
4369
                'email' => 'fromuser@something.EXAMPLE.com',
4370
                'config' => '.example.com test.com',
4371
                'result' => false
4372
            ],
4373
            // Test from email is not in allowed domain.
4374
            [   'email' => 'fromuser@moodle.com',
4375
                'config' => 'example.com test.com',
4376
                'result' => get_string('emailonlyallowed', '', 'example.com test.com')
4377
            ],
4378
            // Test from email is not in allowed subdomain.
4379
            [   'email' => 'fromuser@something.example.com',
4380
                'config' => 'example.com test.com',
4381
                'result' => get_string('emailonlyallowed', '', 'example.com test.com')
4382
            ],
4383
        ];
4384
    }
4385
 
4386
    /**
4387
     * Test denyemailaddresses setting.
4388
     *
4389
     * @param string $email Email address for the from user.
4390
     * @param string $config The CFG->denyemailaddresses config values
4391
     * @param false/string $result The expected result.
4392
     *
4393
     * @dataProvider data_email_is_not_allowed_for_denyemailaddresses
4394
     */
11 efrain 4395
    public function test_email_is_not_allowed_for_denyemailaddresses($email, $config, $result): void {
1 efrain 4396
        $this->resetAfterTest();
4397
 
4398
        set_config('denyemailaddresses', $config);
4399
        $this->assertEquals($result, email_is_not_allowed($email));
4400
    }
4401
 
4402
 
4403
    /**
4404
     * Data provider for test_email_is_not_allowed_for_denyemailaddresses.
4405
     *
4406
     * @return array Returns an array of test data for the above function.
4407
     */
1441 ariadna 4408
    public static function data_email_is_not_allowed_for_denyemailaddresses(): array {
1 efrain 4409
        return [
4410
            // Test denied domain empty list.
4411
            [
4412
                'email' => 'fromuser@example.com',
4413
                'config' => '',
4414
                'result' => false
4415
            ],
4416
            // Test from email is in denied domain.
4417
            [
4418
                'email' => 'fromuser@example.com',
4419
                'config' => 'example.com test.com',
4420
                'result' => get_string('emailnotallowed', '', 'example.com test.com')
4421
            ],
4422
            // Test from email is in denied domain but uppercase config.
4423
            [
4424
                'email' => 'fromuser@example.com',
4425
                'config' => 'EXAMPLE.com test.com',
4426
                'result' => get_string('emailnotallowed', '', 'EXAMPLE.com test.com')
4427
            ],
4428
            // Test from email is in denied domain but uppercase email.
4429
            [
4430
                'email' => 'fromuser@EXAMPLE.com',
4431
                'config' => 'example.com test.com',
4432
                'result' => get_string('emailnotallowed', '', 'example.com test.com')
4433
            ],
4434
            // Test from email is in denied subdomain.
4435
            [
4436
                'email' => 'fromuser@something.example.com',
4437
                'config' => '.example.com test.com',
4438
                'result' => get_string('emailnotallowed', '', '.example.com test.com')
4439
            ],
4440
            // Test from email is in denied subdomain but uppercase config.
4441
            [
4442
                'email' => 'fromuser@something.example.com',
4443
                'config' => '.EXAMPLE.com test.com',
4444
                'result' => get_string('emailnotallowed', '', '.EXAMPLE.com test.com')
4445
            ],
4446
            // Test from email is in denied subdomain but uppercase email.
4447
            [
4448
                'email' => 'fromuser@something.EXAMPLE.com',
4449
                'config' => '.example.com test.com',
4450
                'result' => get_string('emailnotallowed', '', '.example.com test.com')
4451
            ],
4452
            // Test from email is not in denied domain.
4453
            [   'email' => 'fromuser@moodle.com',
4454
                'config' => 'example.com test.com',
4455
                'result' => false
4456
            ],
4457
            // Test from email is not in denied subdomain.
4458
            [   'email' => 'fromuser@something.example.com',
4459
                'config' => 'example.com test.com',
4460
                'result' => false
4461
            ],
4462
        ];
4463
    }
4464
 
4465
    /**
4466
     * Test safe method unserialize_array().
4467
     */
11 efrain 4468
    public function test_unserialize_array(): void {
1 efrain 4469
        $a = [1, 2, 3];
4470
        $this->assertEquals($a, unserialize_array(serialize($a)));
4471
        $a = ['a' => 1, 2 => 2, 'b' => 'cde'];
4472
        $this->assertEquals($a, unserialize_array(serialize($a)));
4473
        $a = ['a' => 1, 2 => 2, 'b' => 'c"d"e'];
4474
        $this->assertEquals($a, unserialize_array(serialize($a)));
4475
        $a = ['a' => 1, 2 => ['c' => 'd', 'e' => 'f'], 'b' => 'cde'];
4476
        $this->assertEquals($a, unserialize_array(serialize($a)));
4477
        $a = ['a' => 1, 2 => ['c' => 'd', 'e' => ['f' => 'g']], 'b' => 'cde'];
4478
        $this->assertEquals($a, unserialize_array(serialize($a)));
4479
        $a = ['a' => 1, 2 => 2, 'b' => 'c"d";e'];
4480
        $this->assertEquals($a, unserialize_array(serialize($a)));
4481
 
4482
        // Can not unserialize if there are any objects.
4483
        $a = (object)['a' => 1, 2 => 2, 'b' => 'cde'];
4484
        $this->assertFalse(unserialize_array(serialize($a)));
4485
        $a = ['a' => 1, 2 => 2, 'b' => (object)['a' => 'cde']];
4486
        $this->assertFalse(unserialize_array(serialize($a)));
4487
        $a = ['a' => 1, 2 => 2, 'b' => ['c' => (object)['a' => 'cde']]];
4488
        $this->assertFalse(unserialize_array(serialize($a)));
4489
        $a = ['a' => 1, 2 => 2, 'b' => ['c' => new lang_string('no')]];
4490
        $this->assertFalse(unserialize_array(serialize($a)));
4491
 
4492
        // Array used in the grader report.
4493
        $a = array('aggregatesonly' => [51, 34], 'gradesonly' => [21, 45, 78]);
4494
        $this->assertEquals($a, unserialize_array(serialize($a)));
4495
    }
4496
 
4497
    /**
4498
     * Test method for safely unserializing a serialized object of type stdClass
4499
     */
4500
    public function test_unserialize_object(): void {
4501
        $object = (object) [
4502
            'foo' => 42,
4503
            'bar' => 'Hamster',
4504
            'innerobject' => (object) [
4505
                'baz' => 'happy',
4506
            ],
4507
        ];
4508
 
4509
        // We should get back the same object we serialized.
4510
        $serializedobject = serialize($object);
4511
        $this->assertEquals($object, unserialize_object($serializedobject));
4512
 
4513
        // Try serializing a different class, not allowed.
4514
        $langstr = new lang_string('no');
4515
        $serializedlangstr = serialize($langstr);
4516
        $unserializedlangstr = unserialize_object($serializedlangstr);
4517
        $this->assertInstanceOf(\stdClass::class, $unserializedlangstr);
4518
    }
4519
 
4520
    /**
4521
     * Test that the component_class_callback returns the correct default value when the class was not found.
4522
     *
4523
     * @dataProvider component_class_callback_default_provider
4524
     * @param $default
4525
     */
11 efrain 4526
    public function test_component_class_callback_not_found($default): void {
1 efrain 4527
        $this->assertSame($default, component_class_callback('thisIsNotTheClassYouWereLookingFor', 'anymethod', [], $default));
4528
    }
4529
 
4530
    /**
4531
     * Test that the component_class_callback returns the correct default value when the class was not found.
4532
     *
4533
     * @dataProvider component_class_callback_default_provider
4534
     * @param $default
4535
     */
11 efrain 4536
    public function test_component_class_callback_method_not_found($default): void {
1 efrain 4537
        require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
4538
 
4539
        $this->assertSame($default, component_class_callback(test_component_class_callback_example::class, 'this_is_not_the_method_you_were_looking_for', ['abc'], $default));
4540
    }
4541
 
4542
    /**
4543
     * Test that the component_class_callback returns the default when the method returned null.
4544
     *
4545
     * @dataProvider component_class_callback_default_provider
4546
     * @param $default
4547
     */
11 efrain 4548
    public function test_component_class_callback_found_returns_null($default): void {
1 efrain 4549
        require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
4550
 
4551
        $this->assertSame($default, component_class_callback(\test_component_class_callback_example::class, 'method_returns_value', [null], $default));
4552
        $this->assertSame($default, component_class_callback(\test_component_class_callback_child_example::class, 'method_returns_value', [null], $default));
4553
    }
4554
 
4555
    /**
4556
     * Test that the component_class_callback returns the expected value and not the default when there was a value.
4557
     *
4558
     * @dataProvider component_class_callback_data_provider
4559
     * @param $default
4560
     */
11 efrain 4561
    public function test_component_class_callback_found_returns_value($value): void {
1 efrain 4562
        require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
4563
 
4564
        $this->assertSame($value, component_class_callback(\test_component_class_callback_example::class, 'method_returns_value', [$value], 'This is not the value you were looking for'));
4565
        $this->assertSame($value, component_class_callback(\test_component_class_callback_child_example::class, 'method_returns_value', [$value], 'This is not the value you were looking for'));
4566
    }
4567
 
4568
    /**
4569
     * Test that the component_class_callback handles multiple params correctly.
4570
     *
4571
     * @dataProvider component_class_callback_multiple_params_provider
4572
     * @param $default
4573
     */
11 efrain 4574
    public function test_component_class_callback_found_accepts_multiple($params, $count): void {
1 efrain 4575
        require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
4576
 
4577
        $this->assertSame($count, component_class_callback(\test_component_class_callback_example::class, 'method_returns_all_params', $params, 'This is not the value you were looking for'));
4578
        $this->assertSame($count, component_class_callback(\test_component_class_callback_child_example::class, 'method_returns_all_params', $params, 'This is not the value you were looking for'));
4579
    }
4580
 
4581
    /**
4582
     * Data provider with list of default values for user in component_class_callback tests.
4583
     *
4584
     * @return array
4585
     */
1441 ariadna 4586
    public static function component_class_callback_default_provider(): array {
1 efrain 4587
        return [
4588
            'null' => [null],
4589
            'empty string' => [''],
4590
            'string' => ['This is a string'],
4591
            'int' => [12345],
4592
            'stdClass' => [(object) ['this is my content']],
4593
            'array' => [['a' => 'b',]],
4594
        ];
4595
    }
4596
 
4597
    /**
4598
     * Data provider with list of default values for user in component_class_callback tests.
4599
     *
4600
     * @return array
4601
     */
1441 ariadna 4602
    public static function component_class_callback_data_provider(): array {
1 efrain 4603
        return [
4604
            'empty string' => [''],
4605
            'string' => ['This is a string'],
4606
            'int' => [12345],
4607
            'stdClass' => [(object) ['this is my content']],
4608
            'array' => [['a' => 'b',]],
4609
        ];
4610
    }
4611
 
4612
    /**
4613
     * Data provider with list of default values for user in component_class_callback tests.
4614
     *
4615
     * @return array
4616
     */
1441 ariadna 4617
    public static function component_class_callback_multiple_params_provider(): array {
1 efrain 4618
        return [
4619
            'empty array' => [
4620
                [],
4621
                0,
4622
            ],
4623
            'string value' => [
4624
                ['one'],
4625
                1,
4626
            ],
4627
            'string values' => [
4628
                ['one', 'two'],
4629
                2,
4630
            ],
4631
            'arrays' => [
4632
                [[], []],
4633
                2,
4634
            ],
4635
            'nulls' => [
4636
                [null, null, null, null],
4637
                4,
4638
            ],
4639
            'mixed' => [
4640
                ['a', 1, null, (object) [], []],
4641
                5,
4642
            ],
4643
        ];
4644
    }
4645
 
4646
    /**
4647
     * Test that {@link get_callable_name()} describes the callable as expected.
4648
     *
4649
     * @dataProvider callable_names_provider
4650
     * @param callable $callable
4651
     * @param string $expectedname
4652
     */
11 efrain 4653
    public function test_get_callable_name($callable, $expectedname): void {
1 efrain 4654
        $this->assertSame($expectedname, get_callable_name($callable));
4655
    }
4656
 
4657
    /**
4658
     * Provides a set of callables and their human readable names.
4659
     *
4660
     * @return array of (string)case => [(mixed)callable, (string|bool)expected description]
4661
     */
1441 ariadna 4662
    public static function callable_names_provider(): array {
1 efrain 4663
        return [
4664
            'integer' => [
4665
                386,
4666
                false,
4667
            ],
4668
            'boolean' => [
4669
                true,
4670
                false,
4671
            ],
4672
            'static_method_as_literal' => [
4673
                'my_foobar_class::my_foobar_method',
4674
                'my_foobar_class::my_foobar_method',
4675
            ],
4676
            'static_method_of_literal_class' => [
4677
                ['my_foobar_class', 'my_foobar_method'],
4678
                'my_foobar_class::my_foobar_method',
4679
            ],
4680
            'method_of_object' => [
4681
                [new lang_string('parentlanguage', 'core_langconfig'), 'my_foobar_method'],
1441 ariadna 4682
                'core\lang_string::my_foobar_method',
1 efrain 4683
            ],
4684
            'function_as_literal' => [
4685
                'my_foobar_callback',
4686
                'my_foobar_callback',
4687
            ],
4688
            'function_as_closure' => [
4689
                function($a) { return $a; },
4690
                'Closure::__invoke',
4691
            ],
4692
        ];
4693
    }
4694
 
4695
    /**
1441 ariadna 4696
     * Test that get_callable_name works with a static method on an instance.
4697
     */
4698
    public function test_get_callable_name_this(): void {
4699
        $this->assertSame('core\moodlelib_test::foo', get_callable_name([$this, 'foo']));
4700
    }
4701
 
4702
    /**
1 efrain 4703
     * Data provider for \core_moodlelib_testcase::test_get_complete_user_data().
4704
     *
4705
     * @return array
4706
     */
1441 ariadna 4707
    public static function user_data_provider(): array {
1 efrain 4708
        return [
4709
            'Fetch data using a valid username' => [
4710
                'username', 's1', true
4711
            ],
4712
            'Fetch data using a valid username, different case' => [
4713
                'username', 'S1', true
4714
            ],
4715
            'Fetch data using a valid username, different case for fieldname and value' => [
4716
                'USERNAME', 'S1', true
4717
            ],
4718
            'Fetch data using an invalid username' => [
4719
                'username', 's2', false
4720
            ],
4721
            'Fetch by email' => [
4722
                'email', 's1@example.com', true
4723
            ],
4724
            'Fetch data using a non-existent email' => [
4725
                'email', 's2@example.com', false
4726
            ],
4727
            'Fetch data using a non-existent email, throw exception' => [
4728
                'email', 's2@example.com', false, \dml_missing_record_exception::class
4729
            ],
4730
            'Multiple accounts with the same email' => [
4731
                'email', 's1@example.com', false, 1
4732
            ],
4733
            'Multiple accounts with the same email, throw exception' => [
4734
                'email', 's1@example.com', false, 1, \dml_multiple_records_exception::class
4735
            ],
4736
            'Fetch data using a valid user ID' => [
4737
                'id', true, true
4738
            ],
4739
            'Fetch data using a non-existent user ID' => [
4740
                'id', false, false
4741
            ],
4742
        ];
4743
    }
4744
 
4745
    /**
4746
     * Test for get_complete_user_data().
4747
     *
4748
     * @dataProvider user_data_provider
4749
     * @param string $field The field to use for the query.
4750
     * @param string|boolean $value The field value. When fetching by ID, set true to fetch valid user ID, false otherwise.
4751
     * @param boolean $success Whether we expect for the fetch to succeed or return false.
4752
     * @param int $allowaccountssameemail Value for $CFG->allowaccountssameemail.
4753
     * @param string $expectedexception The exception to be expected.
4754
     */
11 efrain 4755
    public function test_get_complete_user_data($field, $value, $success, $allowaccountssameemail = 0, $expectedexception = ''): void {
1 efrain 4756
        $this->resetAfterTest();
4757
 
4758
        // Set config settings we need for our environment.
4759
        set_config('allowaccountssameemail', $allowaccountssameemail);
4760
 
4761
        // Generate the user data.
4762
        $generator = $this->getDataGenerator();
4763
        $userdata = [
4764
            'username' => 's1',
4765
            'email' => 's1@example.com',
4766
        ];
4767
        $user = $generator->create_user($userdata);
4768
 
4769
        if ($allowaccountssameemail) {
4770
            // Create another user with the same email address.
4771
            $generator->create_user(['email' => 's1@example.com']);
4772
        }
4773
 
4774
        // Since the data provider can't know what user ID to use, do a special handling for ID field tests.
4775
        if ($field === 'id') {
4776
            if ($value) {
4777
                // Test for fetching data using a valid user ID. Use the generated user's ID.
4778
                $value = $user->id;
4779
            } else {
4780
                // Test for fetching data using a non-existent user ID.
4781
                $value = $user->id + 1;
4782
            }
4783
        }
4784
 
4785
        // When an exception is expected.
4786
        $throwexception = false;
4787
        if ($expectedexception) {
4788
            $this->expectException($expectedexception);
4789
            $throwexception = true;
4790
        }
4791
 
4792
        $fetcheduser = get_complete_user_data($field, $value, null, $throwexception);
4793
        if ($success) {
4794
            $this->assertEquals($user->id, $fetcheduser->id);
4795
            $this->assertEquals($user->username, $fetcheduser->username);
4796
            $this->assertEquals($user->email, $fetcheduser->email);
4797
        } else {
4798
            $this->assertFalse($fetcheduser);
4799
        }
4800
    }
4801
 
4802
    /**
4803
     * Test for send_password_change_().
4804
     */
11 efrain 4805
    public function test_send_password_change_info(): void {
1 efrain 4806
        $this->resetAfterTest();
4807
 
4808
        $user = $this->getDataGenerator()->create_user();
4809
 
4810
        $sink = $this->redirectEmails(); // Make sure we are redirecting emails.
4811
        send_password_change_info($user);
4812
        $result = $sink->get_messages();
4813
        $sink->close();
4814
 
4815
        $this->assertStringContainsString('passwords cannot be reset on this site', quoted_printable_decode($result[0]->body));
4816
    }
4817
 
4818
    /**
4819
     * Test the get_time_interval_string for a range of inputs.
4820
     *
4821
     * @dataProvider get_time_interval_string_provider
4822
     * @param int $time1 the time1 param.
4823
     * @param int $time2 the time2 param.
4824
     * @param string|null $format the format param.
4825
     * @param string $expected the expected string.
4826
     * @param bool $dropzeroes the value passed for the `$dropzeros` param.
4827
     * @param bool $fullformat the value passed for the `$fullformat` param.
4828
     * @covers \get_time_interval_string
4829
     */
4830
    public function test_get_time_interval_string(int $time1, int $time2, ?string $format, string $expected,
11 efrain 4831
            bool $dropzeroes = false, bool $fullformat = false): void {
1 efrain 4832
        if (is_null($format)) {
4833
            $this->assertEquals($expected, get_time_interval_string($time1, $time2));
4834
        } else {
4835
            $this->assertEquals($expected, get_time_interval_string($time1, $time2, $format, $dropzeroes, $fullformat));
4836
        }
4837
    }
4838
 
4839
    /**
4840
     * Data provider for the test_get_time_interval_string() method.
4841
     */
1441 ariadna 4842
    public static function get_time_interval_string_provider(): array {
1 efrain 4843
        return [
4844
            'Time is after the reference time by 1 minute, omitted format' => [
4845
                'time1' => 12345660,
4846
                'time2' => 12345600,
4847
                'format' => null,
4848
                'expected' => '0d 0h 1m'
4849
            ],
4850
            'Time is before the reference time by 1 minute, omitted format' => [
4851
                'time1' => 12345540,
4852
                'time2' => 12345600,
4853
                'format' => null,
4854
                'expected' => '0d 0h 1m'
4855
            ],
4856
            'Time is equal to the reference time, omitted format' => [
4857
                'time1' => 12345600,
4858
                'time2' => 12345600,
4859
                'format' => null,
4860
                'expected' => '0d 0h 0m'
4861
            ],
4862
            'Time is after the reference time by 1 minute, empty string format' => [
4863
                'time1' => 12345660,
4864
                'time2' => 12345600,
4865
                'format' => '',
4866
                'expected' => '0d 0h 1m'
4867
            ],
4868
            'Time is before the reference time by 1 minute, empty string format' => [
4869
                'time1' => 12345540,
4870
                'time2' => 12345600,
4871
                'format' => '',
4872
                'expected' => '0d 0h 1m'
4873
            ],
4874
            'Time is equal to the reference time, empty string format' => [
4875
                'time1' => 12345600,
4876
                'time2' => 12345600,
4877
                'format' => '',
4878
                'expected' => '0d 0h 0m'
4879
            ],
4880
            'Time is after the reference time by 1 minute, custom format' => [
4881
                'time1' => 12345660,
4882
                'time2' => 12345600,
4883
                'format' => '%R%adays %hhours %imins',
4884
                'expected' => '+0days 0hours 1mins'
4885
            ],
4886
            'Time is before the reference time by 1 minute, custom format' => [
4887
                'time1' => 12345540,
4888
                'time2' => 12345600,
4889
                'format' => '%R%adays %hhours %imins',
4890
                'expected' => '-0days 0hours 1mins'
4891
            ],
4892
            'Time is equal to the reference time, custom format' => [
4893
                'time1' => 12345600,
4894
                'time2' => 12345600,
4895
                'format' => '%R%adays %hhours %imins',
4896
                'expected' => '+0days 0hours 0mins'
4897
            ],
4898
            'Default format, time is after the reference time by 1 minute, drop zeroes, short form' => [
4899
                'time1' => 12345660,
4900
                'time2' => 12345600,
4901
                'format' => '',
4902
                'expected' => '1m',
4903
                'dropzeroes' => true,
4904
            ],
4905
            'Default format, time is after the reference time by 1 minute, drop zeroes, full form' => [
4906
                'time1' => 12345660,
4907
                'time2' => 12345600,
4908
                'format' => '',
4909
                'expected' => '1 minutes',
4910
                'dropzeroes' => true,
4911
                'fullformat' => true,
4912
            ],
4913
            'Default format, time is after the reference time by 1 minute, retain zeroes, full form' => [
4914
                'time1' => 12345660,
4915
                'time2' => 12345600,
4916
                'format' => '',
4917
                'expected' => '0 days 0 hours 1 minutes',
4918
                'dropzeroes' => false,
4919
                'fullformat' => true,
4920
            ],
4921
            'Empty string format, time is after the reference time by 1 minute, retain zeroes, full form' => [
4922
                'time1' => 12345660,
4923
                'time2' => 12345600,
4924
                'format' => '     ',
4925
                'expected' => '0 days 0 hours 1 minutes',
4926
                'dropzeroes' => false,
4927
                'fullformat' => true,
4928
            ],
4929
        ];
4930
    }
4931
 
4932
    /**
4933
     * Tests the rename_to_unused_name function with a file.
4934
     */
11 efrain 4935
    public function test_rename_to_unused_name_file(): void {
1 efrain 4936
        global $CFG;
4937
 
4938
        // Create a new file in dataroot.
4939
        $file = $CFG->dataroot . '/argh.txt';
4940
        file_put_contents($file, 'Frogs');
4941
 
4942
        // Rename it.
4943
        $newname = rename_to_unused_name($file);
4944
 
4945
        // Check new name has expected format.
4946
        $this->assertMatchesRegularExpression('~/_temp_[a-f0-9]+$~', $newname);
4947
 
4948
        // Check it's still in the same folder.
4949
        $this->assertEquals($CFG->dataroot, dirname($newname));
4950
 
4951
        // Check file can be loaded.
4952
        $this->assertEquals('Frogs', file_get_contents($newname));
4953
 
4954
        // OK, delete the file.
4955
        unlink($newname);
4956
    }
4957
 
4958
    /**
4959
     * Tests the rename_to_unused_name function with a directory.
4960
     */
11 efrain 4961
    public function test_rename_to_unused_name_dir(): void {
1 efrain 4962
        global $CFG;
4963
 
4964
        // Create a new directory in dataroot.
4965
        $file = $CFG->dataroot . '/arghdir';
4966
        mkdir($file);
4967
 
4968
        // Rename it.
4969
        $newname = rename_to_unused_name($file);
4970
 
4971
        // Check new name has expected format.
4972
        $this->assertMatchesRegularExpression('~/_temp_[a-f0-9]+$~', $newname);
4973
 
4974
        // Check it's still in the same folder.
4975
        $this->assertEquals($CFG->dataroot, dirname($newname));
4976
 
4977
        // Check it's still a directory
4978
        $this->assertTrue(is_dir($newname));
4979
 
4980
        // OK, delete the directory.
4981
        rmdir($newname);
4982
    }
4983
 
4984
    /**
4985
     * Tests the rename_to_unused_name function with error cases.
4986
     */
11 efrain 4987
    public function test_rename_to_unused_name_failure(): void {
1 efrain 4988
        global $CFG;
4989
 
4990
        // Rename a file that doesn't exist.
4991
        $file = $CFG->dataroot . '/argh.txt';
4992
        $this->assertFalse(rename_to_unused_name($file));
4993
    }
4994
 
4995
    /**
4996
     * Provider for display_size
4997
     *
4998
     * @return array of ($size, $expected)
4999
     */
1441 ariadna 5000
    public static function display_size_provider(): array {
1 efrain 5001
 
5002
        return [
5003
            [0, '0 bytes'],
5004
            [1, '1 bytes'],
5005
            [1023, '1023 bytes'],
5006
            [1024, '1.0 KB'],
5007
            [2222, '2.2 KB'],
5008
            [33333, '32.6 KB'],
5009
            [444444, '434.0 KB'],
5010
            [5555555, '5.3 MB'],
5011
            [66666666, '63.6 MB'],
5012
            [777777777, '741.7 MB'],
5013
            [8888888888, '8.3 GB'],
5014
            [99999999999, '93.1 GB'],
5015
            [111111111111, '103.5 GB'],
5016
            [2222222222222, '2.0 TB'],
5017
            [33333333333333, '30.3 TB'],
5018
            [444444444444444, '404.2 TB'],
5019
            [5555555555555555, '4.9 PB'],
5020
            [66666666666666666, '59.2 PB'],
5021
            [777777777777777777, '690.8 PB'],
5022
        ];
5023
    }
5024
 
5025
    /**
5026
     * Test display_size
5027
     * @dataProvider display_size_provider
5028
     * @param int $size the size in bytes
5029
     * @param string $expected the expected string.
5030
     */
11 efrain 5031
    public function test_display_size($size, $expected): void {
1 efrain 5032
        $result = display_size($size);
5033
        $expected = str_replace(' ', "\xc2\xa0", $expected); // Should be non-breaking space.
5034
        $this->assertEquals($expected, $result);
5035
    }
5036
 
5037
    /**
5038
     * Provider for display_size using fixed units.
5039
     *
5040
     * @return array of ($size, $units, $expected)
5041
     */
1441 ariadna 5042
    public static function display_size_fixed_provider(): array {
1 efrain 5043
        return [
5044
            [0, 'KB', '0.0 KB'],
5045
            [1, 'MB', '0.0 MB'],
5046
            [777777777, 'GB', '0.7 GB'],
5047
            [8888888888, 'PB', '0.0 PB'],
5048
            [99999999999, 'TB', '0.1 TB'],
5049
            [99999999999, 'B', '99999999999 bytes'],
5050
        ];
5051
    }
5052
 
5053
    /**
5054
     * Test display_size using fixed units.
5055
     *
5056
     * @dataProvider display_size_fixed_provider
5057
     * @param int $size Size in bytes
5058
     * @param string $units Fixed units
5059
     * @param string $expected Expected string.
5060
     */
5061
    public function test_display_size_fixed(int $size, string $units, string $expected): void {
5062
        $result = display_size($size, 1, $units);
5063
        $expected = str_replace(' ', "\xc2\xa0", $expected); // Should be non-breaking space.
5064
        $this->assertEquals($expected, $result);
5065
    }
5066
 
5067
    /**
5068
     * Provider for display_size using specified decimal places.
5069
     *
5070
     * @return array of ($size, $decimalplaces, $units, $expected)
5071
     */
1441 ariadna 5072
    public static function display_size_dp_provider(): array {
1 efrain 5073
        return [
5074
            [0, 1, 'KB', '0.0 KB'],
5075
            [1, 6, 'MB', '0.000001 MB'],
5076
            [777777777, 0, 'GB', '1 GB'],
5077
            [777777777, 0, '', '742 MB'],
5078
            [42, 6, '', '42 bytes'],
5079
        ];
5080
    }
5081
 
5082
    /**
5083
     * Test display_size using specified decimal places.
5084
     *
5085
     * @dataProvider display_size_dp_provider
5086
     * @param int $size Size in bytes
5087
     * @param int $places Number of decimal places
5088
     * @param string $units Fixed units
5089
     * @param string $expected Expected string.
5090
     */
5091
    public function test_display_size_dp(int $size, int $places, string $units, string $expected): void {
5092
        $result = display_size($size, $places, $units);
5093
        $expected = str_replace(' ', "\xc2\xa0", $expected); // Should be non-breaking space.
5094
        $this->assertEquals($expected, $result);
5095
    }
5096
 
5097
    /**
5098
     * Test that the get_list_of_plugins function includes/excludes directories as appropriate.
5099
     *
5100
     * @dataProvider get_list_of_plugins_provider
5101
     * @param   array $expectedlist The expected list of folders
5102
     * @param   array $content The list of file content to set up in the virtual file root
5103
     * @param   string $dir The base dir to look at in the virtual file root
5104
     * @param   string $exclude Any additional folder to exclude
5105
     */
5106
    public function test_get_list_of_plugins(array $expectedlist, array $content, string $dir, string $exclude): void {
5107
        $vfileroot = \org\bovigo\vfs\vfsStream::setup('root', null, $content);
5108
        $base = \org\bovigo\vfs\vfsStream::url('root');
5109
 
5110
        $this->assertEquals($expectedlist, get_list_of_plugins($dir, $exclude, $base));
5111
    }
5112
 
5113
    /**
5114
     * Data provider for get_list_of_plugins checks.
5115
     *
5116
     * @return  array
5117
     */
1441 ariadna 5118
    public static function get_list_of_plugins_provider(): array {
1 efrain 5119
        return [
5120
            'Standard excludes' => [
5121
                ['amdd', 'class', 'local', 'test'],
5122
                [
5123
                    '.' => [],
5124
                    '..' => [],
5125
                    'amd' => [],
5126
                    'amdd' => [],
5127
                    'class' => [],
5128
                    'classes' => [],
5129
                    'local' => [],
5130
                    'test' => [],
5131
                    'tests' => [],
5132
                    'yui' => [],
5133
                ],
5134
                '',
5135
                '',
5136
            ],
5137
            'Standard excludes with addition' => [
5138
                ['amdd', 'local', 'test'],
5139
                [
5140
                    '.' => [],
5141
                    '..' => [],
5142
                    'amd' => [],
5143
                    'amdd' => [],
5144
                    'class' => [],
5145
                    'classes' => [],
5146
                    'local' => [],
5147
                    'test' => [],
5148
                    'tests' => [],
5149
                    'yui' => [],
5150
                ],
5151
                '',
5152
                'class',
5153
            ],
5154
            'Files excluded' => [
5155
                ['def'],
5156
                [
5157
                    '.' => [],
5158
                    '..' => [],
5159
                    'abc' => 'File with filename abc',
5160
                    'def' => [
5161
                        '.' => [],
5162
                        '..' => [],
5163
                        'example.txt' => 'In a directory called "def"',
5164
                    ],
5165
                ],
5166
                '',
5167
                '',
5168
            ],
5169
            'Subdirectories only' => [
5170
                ['abc'],
5171
                [
5172
                    '.' => [],
5173
                    '..' => [],
5174
                    'foo' => [
5175
                        '.' => [],
5176
                        '..' => [],
5177
                        'abc' => [],
5178
                    ],
5179
                    'bar' => [
5180
                        '.' => [],
5181
                        '..' => [],
5182
                        'def' => [],
5183
                    ],
5184
                ],
5185
                'foo',
5186
                '',
5187
            ],
5188
        ];
5189
    }
5190
 
5191
    /**
5192
     * Test get_home_page() method.
5193
     *
5194
     * @dataProvider get_home_page_provider
5195
     * @param string $user Whether the user is logged, guest or not logged.
5196
     * @param int $expected Expected value after calling the get_home_page method.
1441 ariadna 5197
     * @param int|string|null $defaulthomepage The $CFG->defaulthomepage setting value.
5198
     * @param int|null $enabledashboard Whether the dashboard should be enabled or not.
5199
     * @param int|string|null $userpreference User preference for the home page setting.
5200
     * $param int|null $allowguestmymoodle The $CFG->allowguestmymoodle setting value.
1 efrain 5201
     * @covers ::get_home_page
5202
     */
1441 ariadna 5203
    public function test_get_home_page(
5204
        string $user,
5205
        int $expected,
5206
        int|string|null $defaulthomepage = null,
5207
        ?int $enabledashboard = null,
5208
        int|string|null $userpreference = null,
5209
        ?int $allowguestmymoodle = null,
5210
    ): void {
1 efrain 5211
        global $CFG, $USER;
5212
 
5213
        $this->resetAfterTest();
5214
 
5215
        if ($user == 'guest') {
5216
            $this->setGuestUser();
5217
        } else if ($user == 'logged') {
5218
            $this->setUser($this->getDataGenerator()->create_user());
5219
        }
5220
 
5221
        if (isset($defaulthomepage)) {
5222
            $CFG->defaulthomepage = $defaulthomepage;
5223
        }
5224
        if (isset($enabledashboard)) {
5225
            $CFG->enabledashboard = $enabledashboard;
5226
        }
1441 ariadna 5227
        if (isset($allowguestmymoodle)) {
5228
            $CFG->allowguestmymoodle = $allowguestmymoodle;
5229
        }
1 efrain 5230
 
5231
        if ($USER) {
5232
            set_user_preferences(['user_home_page_preference' => $userpreference], $USER->id);
5233
        }
5234
 
5235
        $homepage = get_home_page();
5236
        $this->assertEquals($expected, $homepage);
5237
    }
5238
 
5239
    /**
5240
     * Data provider for get_home_page checks.
5241
     *
5242
     * @return array
5243
     */
1441 ariadna 5244
    public static function get_home_page_provider(): array {
5245
        global $CFG;
5246
 
1 efrain 5247
        return [
5248
            'No logged user' => [
5249
                'user' => 'nologged',
5250
                'expected' => HOMEPAGE_SITE,
5251
            ],
1441 ariadna 5252
            'Guest user. Dashboard set as default home page and enabled for guests' => [
1 efrain 5253
                'user' => 'guest',
1441 ariadna 5254
                'expected' => HOMEPAGE_MY,
5255
            ],
5256
            'Guest user. Dashboard set as default home page but disabled for guests' => [
5257
                'user' => 'guest',
1 efrain 5258
                'expected' => HOMEPAGE_SITE,
1441 ariadna 5259
                'defaulthomepage' => HOMEPAGE_MY,
5260
                'enabledashboard' => 1,
5261
                'userpreference' => null,
5262
                'allowguestmymoodle' => 0,
1 efrain 5263
            ],
1441 ariadna 5264
            'Guest user. My courses set as default home page' => [
5265
                'user' => 'guest',
5266
                'expected' => HOMEPAGE_SITE,
5267
                'defaulthomepage' => HOMEPAGE_MYCOURSES,
5268
            ],
5269
            'Guest user. User preference set as default page' => [
5270
                'user' => 'guest',
5271
                'expected' => HOMEPAGE_SITE,
5272
                'defaulthomepage' => HOMEPAGE_USER,
5273
            ],
1 efrain 5274
            'Logged user. Dashboard set as default home page and enabled' => [
5275
                'user' => 'logged',
5276
                'expected' => HOMEPAGE_MY,
5277
            ],
5278
            'Logged user. Dashboard set as default home page but disabled' => [
5279
                'user' => 'logged',
5280
                'expected' => HOMEPAGE_MYCOURSES,
5281
                'defaulthomepage' => HOMEPAGE_MY,
5282
                'enabledashboard' => 0,
5283
            ],
5284
            'Logged user. My courses set as default home page with dashboard enabled' => [
5285
                'user' => 'logged',
5286
                'expected' => HOMEPAGE_MYCOURSES,
5287
                'defaulthomepage' => HOMEPAGE_MYCOURSES,
5288
                'enabledashboard' => 1,
5289
            ],
5290
            'Logged user. My courses set as default home page with dashboard disabled' => [
5291
                'user' => 'logged',
5292
                'expected' => HOMEPAGE_MYCOURSES,
5293
                'defaulthomepage' => HOMEPAGE_MYCOURSES,
5294
                'enabledashboard' => 0,
5295
            ],
5296
            'Logged user. Site set as default home page with dashboard enabled' => [
5297
                'user' => 'logged',
5298
                'expected' => HOMEPAGE_SITE,
5299
                'defaulthomepage' => HOMEPAGE_SITE,
5300
                'enabledashboard' => 1,
5301
            ],
5302
            'Logged user. Site set as default home page with dashboard disabled' => [
5303
                'user' => 'logged',
5304
                'expected' => HOMEPAGE_SITE,
5305
                'defaulthomepage' => HOMEPAGE_SITE,
5306
                'enabledashboard' => 0,
5307
            ],
1441 ariadna 5308
            'Logged user. URL set as default home page.' => [
5309
                'user' => 'logged',
5310
                'expected' => HOMEPAGE_URL,
5311
                'defaulthomepage' => "/home",
5312
            ],
1 efrain 5313
            'Logged user. User preference set as default page with dashboard enabled and user preference set to dashboard' => [
5314
                'user' => 'logged',
5315
                'expected' => HOMEPAGE_MY,
5316
                'defaulthomepage' => HOMEPAGE_USER,
5317
                'enabledashboard' => 1,
5318
                'userpreference' => HOMEPAGE_MY,
5319
            ],
5320
            'Logged user. User preference set as default page with dashboard disabled and user preference set to dashboard' => [
5321
                'user' => 'logged',
5322
                'expected' => HOMEPAGE_MYCOURSES,
5323
                'defaulthomepage' => HOMEPAGE_USER,
5324
                'enabledashboard' => 0,
5325
                'userpreference' => HOMEPAGE_MY,
5326
            ],
5327
            'Logged user. User preference set as default page with dashboard enabled and user preference set to my courses' => [
5328
                'user' => 'logged',
5329
                'expected' => HOMEPAGE_MYCOURSES,
5330
                'defaulthomepage' => HOMEPAGE_USER,
5331
                'enabledashboard' => 1,
5332
                'userpreference' => HOMEPAGE_MYCOURSES,
5333
            ],
5334
            'Logged user. User preference set as default page with dashboard disabled and user preference set to my courses' => [
5335
                'user' => 'logged',
5336
                'expected' => HOMEPAGE_MYCOURSES,
5337
                'defaulthomepage' => HOMEPAGE_USER,
5338
                'enabledashboard' => 0,
5339
                'userpreference' => HOMEPAGE_MYCOURSES,
5340
            ],
1441 ariadna 5341
            'Logged user. User preference set as default page with user preference set to URL.' => [
5342
                'user' => 'logged',
5343
                'expected' => HOMEPAGE_URL,
5344
                'defaulthomepage' => HOMEPAGE_USER,
5345
                'enabledashboard' => null,
5346
                'userpreference' => "/home",
5347
            ],
1 efrain 5348
        ];
5349
    }
5350
 
5351
    /**
5352
     * Test get_default_home_page() method.
5353
     *
5354
     * @covers ::get_default_home_page
5355
     */
11 efrain 5356
    public function test_get_default_home_page(): void {
1 efrain 5357
        global $CFG;
5358
 
5359
        $this->resetAfterTest();
5360
 
5361
        $CFG->enabledashboard = 1;
5362
        $default = get_default_home_page();
5363
        $this->assertEquals(HOMEPAGE_MY, $default);
5364
 
5365
        $CFG->enabledashboard = 0;
5366
        $default = get_default_home_page();
5367
        $this->assertEquals(HOMEPAGE_MYCOURSES, $default);
5368
    }
5369
 
5370
    /**
1441 ariadna 5371
     * Test getting default home page for {@see HOMEPAGE_URL}
5372
     *
5373
     * @covers ::get_default_home_page_url
5374
     */
5375
    public function test_get_default_home_page_url(): void {
5376
        global $CFG;
5377
 
5378
        $this->resetAfterTest();
5379
        $this->setAdminUser();
5380
 
5381
        $this->assertNull(get_default_home_page_url());
5382
 
5383
        // Site configuration.
5384
        $CFG->defaulthomepage = "/home";
5385
        $this->assertEquals("{$CFG->wwwroot}/home", get_default_home_page_url());
5386
 
5387
        // Site configuration with invalid value.
5388
        $CFG->defaulthomepage = "home";
5389
        $this->assertNull(get_default_home_page_url());
5390
 
5391
        // User preference.
5392
        $CFG->defaulthomepage = HOMEPAGE_USER;
5393
 
5394
        $userpreference = "/about";
5395
        set_user_preference('user_home_page_preference', $userpreference);
5396
        $this->assertEquals("{$CFG->wwwroot}/about", get_default_home_page_url());
5397
 
5398
        // User preference with invalid value.
5399
        set_user_preference('user_home_page_preference', "about");
5400
        $this->assertNull(get_default_home_page_url());
5401
    }
5402
 
5403
    /**
1 efrain 5404
     * Tests the get_performance_info function with regard to locks.
5405
     *
5406
     * @covers ::get_performance_info
5407
     */
5408
    public function test_get_performance_info_locks(): void {
5409
        global $PERF;
5410
 
5411
        // Unset lock data just in case previous tests have set it.
5412
        unset($PERF->locks);
5413
 
5414
        // With no lock data, there should be no information about locks in the results.
5415
        $result = get_performance_info();
5416
        $this->assertStringNotContainsString('Lock', $result['html']);
5417
        $this->assertStringNotContainsString('Lock', $result['txt']);
5418
 
5419
        // Rather than really do locks, just fill the array with fake data in the right format.
5420
        $PERF->locks = [
5421
            (object) [
5422
                'type' => 'phpunit',
5423
                'resource' => 'lock1',
5424
                'wait' => 0.59,
5425
                'success' => true,
5426
                'held' => '6.04'
5427
            ], (object) [
5428
                'type' => 'phpunit',
5429
                'resource' => 'lock2',
5430
                'wait' => 0.91,
5431
                'success' => false
5432
            ]
5433
        ];
5434
        $result = get_performance_info();
5435
 
5436
        // Extract HTML table rows.
5437
        $this->assertEquals(1, preg_match('~<table class="locktimings.*?</table>~s',
5438
                $result['html'], $matches));
5439
        $this->assertEquals(3, preg_match_all('~<tr[> ].*?</tr>~s', $matches[0], $matches2));
5440
        $rows = $matches2[0];
5441
 
5442
        // Check header.
5443
        $this->assertMatchesRegularExpression('~Lock.*Waited.*Obtained.*Held~s', $rows[0]);
5444
        // Check both locks.
5445
        $this->assertMatchesRegularExpression('~phpunit/lock1.*0\.6.*&#x2713;.*6\.0~s', $rows[1]);
5446
        $this->assertMatchesRegularExpression('~phpunit/lock2.*0\.9.*&#x274c;.*-~s', $rows[2]);
5447
 
5448
        $this->assertStringContainsString('Locks (waited/obtained/held): ' .
5449
                'phpunit/lock1 (0.6/y/6.0) phpunit/lock2 (0.9/n/-).', $result['txt']);
5450
    }
5451
 
5452
    /**
5453
     * Tests the get_performance_info function with regard to session wait time.
5454
     *
5455
     * @covers ::get_performance_info
5456
     */
5457
    public function test_get_performance_info_session_wait(): void {
5458
        global $PERF;
5459
 
5460
        // With no session lock data, there should be no session wait information in the results.
5461
        unset($PERF->sessionlock);
5462
        $result = get_performance_info();
5463
        $this->assertStringNotContainsString('Session wait', $result['html']);
5464
        $this->assertStringNotContainsString('sessionwait', $result['txt']);
5465
 
5466
        // With suitable data, it should be included in the result.
5467
        $PERF->sessionlock = ['wait' => 4.2];
5468
        $result = get_performance_info();
5469
        $this->assertStringContainsString('Session wait: 4.200 secs', $result['html']);
5470
        $this->assertStringContainsString('sessionwait: 4.200 secs', $result['txt']);
5471
    }
5472
 
5473
    /**
5474
     * Test the html_is_blank() function.
5475
     *
5476
     * @covers ::html_is_blank
5477
     */
11 efrain 5478
    public function test_html_is_blank(): void {
1 efrain 5479
        $this->assertEquals(true, html_is_blank(null));
5480
        $this->assertEquals(true, html_is_blank(''));
5481
        $this->assertEquals(true, html_is_blank('<p> </p>'));
5482
        $this->assertEquals(false, html_is_blank('<p>.</p>'));
5483
        $this->assertEquals(false, html_is_blank('<img src="#">'));
5484
    }
5485
 
5486
    /**
5487
     * Provider for is_proxybypass
5488
     *
5489
     * @return array of test cases.
5490
     */
1441 ariadna 5491
    public static function is_proxybypass_provider(): array {
1 efrain 5492
 
5493
        return [
5494
            'Proxybypass contains the same IP as the beginning of the URL' => [
5495
                'http://192.168.5.5-fake-app-7f000101.nip.io',
1441 ariadna 5496
                '192.168.5.5,127.0.0.1',
5497
                false,
1 efrain 5498
            ],
1441 ariadna 5499
            'Proxybypass contains some extra whitespaces (test with hostname)' => [
5500
                'store.mydomain.com',
5501
                'store.mydomain.com , 192.168.5.5',
5502
                false,
5503
            ],
5504
            'Proxybypass contains some extra whitespaces (test with IP)' => [
5505
                '192.168.5.5',
5506
                'store.mydomain.com , 192.168.5.5',
5507
                false,
5508
            ],
1 efrain 5509
            'Proxybypass contains the last part of the URL' => [
5510
                'http://192.168.5.5-fake-app-7f000101.nip.io',
5511
                'app-7f000101.nip.io',
1441 ariadna 5512
                false,
1 efrain 5513
            ],
5514
            'Proxybypass contains the last part of the URL 2' => [
5515
                'http://store.mydomain.com',
5516
                'mydomain.com',
1441 ariadna 5517
                false,
1 efrain 5518
            ],
5519
            'Proxybypass contains part of the url' => [
5520
                'http://myweb.com',
5521
                'store.myweb.com',
1441 ariadna 5522
                false,
1 efrain 5523
            ],
1441 ariadna 5524
            'Proxybypass with a wildcard contains part of the url' => [
5525
                'http://myweb.com',
5526
                '*.myweb.com',
5527
                false,
5528
            ],
1 efrain 5529
            'Different IPs used in proxybypass' => [
5530
                'http://192.168.5.5',
5531
                '192.168.5.3',
1441 ariadna 5532
                false,
1 efrain 5533
            ],
1441 ariadna 5534
            'Different partial IPs used in proxybypass' => [
5535
                'http://192.168.5.5',
5536
                '192.16',
5537
                false,
5538
            ],
5539
            'Different partial IPs used in proxybypass with ending dot' => [
5540
                'http://192.168.5.5',
5541
                '192.16.',
5542
                false,
5543
            ],
1 efrain 5544
            'Proxybypass and URL matchs' => [
5545
                'http://store.mydomain.com',
5546
                'store.mydomain.com',
1441 ariadna 5547
                true,
1 efrain 5548
            ],
1441 ariadna 5549
            'Proxybypass with a wildcard value covers any subdomain' => [
5550
                'http://store.mydomain.com',
5551
                '*.mydomain.com',
5552
                true,
5553
            ],
5554
            'Proxybypass with a wildcard value covers any higher level subdomain' => [
5555
                'http://another.store.mydomain.com',
5556
                '*.mydomain.com',
5557
                true,
5558
            ],
5559
            'Proxybypass with multiple domains' => [
5560
                'http://store.mydomain.com',
5561
                '127.0.0.1,*.mydomain.com',
5562
                true,
5563
            ],
1 efrain 5564
            'IP used in proxybypass' => [
5565
                'http://192.168.5.5',
5566
                '192.168.5.5',
1441 ariadna 5567
                true,
1 efrain 5568
            ],
1441 ariadna 5569
            'Partial IP used in proxybypass' => [
5570
                'http://192.168.5.5',
5571
                '192.168.',
5572
                true,
5573
            ],
1 efrain 5574
        ];
5575
    }
5576
 
5577
    /**
5578
     * Check if $url matches anything in proxybypass list
5579
     *
5580
     * Test function {@see is_proxybypass()}.
5581
     * @dataProvider is_proxybypass_provider
5582
     * @param string $url url to check
5583
     * @param string $proxybypass
5584
     * @param bool $expected Expected value.
5585
     */
5586
    public function test_is_proxybypass(string $url, string $proxybypass, bool $expected): void {
5587
        $this->resetAfterTest();
5588
 
5589
        global $CFG;
5590
        $CFG->proxyhost = '192.168.5.5'; // Test with a fake proxy.
5591
        $CFG->proxybypass = $proxybypass;
5592
 
5593
        $this->assertEquals($expected, is_proxybypass($url));
5594
    }
5595
 
5596
    /**
5597
     * Test that the moodle_array_keys_filter method behaves in the same way
5598
     * that array_keys behaved before Moodle 8.3.
5599
     *
5600
     * @dataProvider moodle_array_keys_filter_provider
5601
     * @param array $array
5602
     * @param mixed $filter
5603
     * @param bool $strict
5604
     * @param array $expected
5605
     * @covers ::moodle_array_keys_filter
5606
     */
5607
    public function test_moodle_array_keys_filter(
5608
        array $array,
5609
        mixed $filter,
5610
        bool $strict,
5611
        array $expected,
5612
    ): void {
5613
        $this->assertSame(
5614
            $expected,
5615
            moodle_array_keys_filter($array, $filter, $strict),
5616
        );
5617
    }
5618
 
5619
    /**
5620
     * Data provider for moodle_array_keys_filter tests.
5621
     *
5622
     * @return array
5623
     */
5624
    public static function moodle_array_keys_filter_provider(): array {
5625
        return [
5626
            [['a', 'b', 'c'], 'b', false, [1]],
5627
            [
5628
                [
5629
                    'alpha' => 'a',
5630
                    'bravo' => 'b',
5631
                    'charlie' => 'c',
5632
                ],
5633
                'b',
5634
                false,
5635
                ['bravo'],
5636
            ],
5637
            [
5638
                [
5639
                    'zero' => 0,
5640
                    'one' => 1,
5641
                    'true' => true,
5642
                ],
5643
                '1',
5644
                false,
5645
                ['one', 'true'],
5646
            ],
5647
            [
5648
                [
5649
                    'zero' => 0,
5650
                    'one' => 1,
5651
                    'true' => true,
5652
                ],
5653
                true,
5654
                false,
5655
                ['one', 'true'],
5656
            ],
5657
            [
5658
                [
5659
                    'zero' => 0,
5660
                    'one' => 1,
5661
                    'true' => true,
5662
                ],
5663
                true,
5664
                true,
5665
                ['true'],
5666
            ],
5667
            [
5668
                [
5669
                    'zero' => 0,
5670
                    'one' => 1,
5671
                    'true' => true,
5672
                ],
5673
                1,
5674
                true,
5675
                ['one'],
5676
            ],
5677
        ];
5678
    }
1441 ariadna 5679
 
5680
    /**
5681
     * Test case for checking the email greetings in various user notification emails.
5682
     *
5683
     * @dataProvider email_greetings_provider
5684
     * @param string $funcname The name of the function to call for sending the email.
5685
     * @param mixed $extra Any extra parameter required by the function.
5686
     * @covers ::send_password_change_info()
5687
     * @covers ::send_confirmation_email()
5688
     * @covers ::setnew_password_and_mail()
5689
     * @covers ::send_password_change_confirmation_email()
5690
     */
5691
    public function test_email_greetings($funcname, $extra): void {
5692
        $this->resetAfterTest();
5693
 
5694
        $user = $this->getDataGenerator()->create_user();
5695
 
5696
        $sink = $this->redirectEmails(); // Make sure we are redirecting emails.
5697
        $funcname($user, $extra);
5698
        $result = $sink->get_messages();
5699
        $sink->close();
5700
        // Test greetings.
5701
        $this->assertStringContainsString('Hi ' . $user->firstname, quoted_printable_decode($result[0]->body));
5702
    }
5703
 
5704
    /**
5705
     * Data provider for test_email_greetings tests.
5706
     *
5707
     * @return array
5708
     */
5709
    public static function email_greetings_provider(): array {
5710
        $extrasendpasswordchangeconfirmationemail = new \stdClass();
5711
        $extrasendpasswordchangeconfirmationemail->token = '123';
5712
 
5713
        return [
5714
            ['send_password_change_info', null],
5715
            ['send_confirmation_email', null],
5716
            ['setnew_password_and_mail', false],
5717
            ['send_password_change_confirmation_email', $extrasendpasswordchangeconfirmationemail],
5718
        ];
5719
    }
5720
 
5721
    /**
5722
     * Test various moodlelib functions when dealing with a deprecated plugin type.
5723
     *
5724
     * @runInSeparateProcess
5725
     *
5726
     * @covers ::get_string_manager
5727
     * @covers ::component_callback_exists
5728
     * @covers ::component_callback
5729
     * @covers ::component_class_callback
5730
     * @covers ::get_plugins_with_function
5731
     * @covers ::get_plugin_list_with_function
5732
     * @return void
5733
     */
5734
    public function test_moodlelib_deprecated_plugintype(): void {
5735
        $this->resetAfterTest();
5736
        global $CFG;
5737
 
5738
        // Inject the 'fake' plugin type and deprecate it.
5739
        // Note: this method of injection is required to ensure core_component fully builds all caches from the ground up,
5740
        // which is necessary to test things like class autoloading, required for class callbacks checks.
5741
        $this->add_full_mocked_plugintype(
5742
            plugintype: 'fake',
5743
            path: 'lib/tests/fixtures/fakeplugins/fake',
5744
        );
5745
        $this->deprecate_full_mocked_plugintype('fake');
5746
 
5747
        // Verify strings can be fetched for deprecated plugins.
5748
        $stringman = get_string_manager();
5749
        $this->assertEquals('Fake full featured plugin', $stringman->get_string('pluginname', 'fake_fullfeatured'));
5750
 
5751
        // Verify callbacks are NOT supported for deprecated plugins (falling back to using the default return).
5752
        $this->assertFalse(component_callback_exists('fake_fullfeatured', 'test_callback'));
5753
        $this->assertEquals('default', component_callback('fake_fullfeatured', 'test_callback', [], 'default'));
5754
        $this->assertEquals('cat', component_class_callback(\fake_fullfeatured\dummy::class, 'class_callback_test', [], 'cat'));
5755
 
5756
        // Unset allversionshash to trigger a plugin functions rebuild, forcing it to pick up the injected mock plugin functions.
5757
        unset($CFG->allversionshash);
5758
        $plugins = get_plugins_with_function('test_callback');
5759
        $this->assertArrayNotHasKey('fake', $plugins);
5760
        $pluginlist = get_plugin_list_with_function('fake', 'test_callback');
5761
        $this->assertArrayNotHasKey('fake_fullfeatured', $pluginlist);
5762
    }
5763
 
5764
    /**
5765
     * Test various moodlelib functions when dealing with a deleted plugin type.
5766
     *
5767
     * @runInSeparateProcess
5768
     *
5769
     * @covers ::get_string_manager
5770
     * @covers ::component_callback_exists
5771
     * @covers ::component_callback
5772
     * @covers ::component_class_callback
5773
     * @covers ::get_plugins_with_function
5774
     * @covers ::get_plugin_list_with_function
5775
     * @return void
5776
     */
5777
    public function test_moodlelib_deleted_plugintype(): void {
5778
        $this->resetAfterTest();
5779
        global $CFG;
5780
 
5781
        // Inject the 'fake' plugin type and flag it as deleted.
5782
        // Note: this deep method of injection is required to ensure core_component fully builds all caches from the ground up,
5783
        // which is necessary to test things like class autoloading, required for class callbacks checks.
5784
        $this->add_full_mocked_plugintype(
5785
            plugintype: 'fake',
5786
            path: 'lib/tests/fixtures/fakeplugins/fake',
5787
        );
5788
        $this->delete_full_mocked_plugintype('fake');
5789
 
5790
        // Verify no string support for deleted plugins.
5791
        $stringman = get_string_manager();
5792
        $this->assertEquals('[[pluginname]]', $stringman->get_string('pluginname', 'fake_fullfeatured'));
5793
        $this->assertDebuggingCalled();
5794
 
5795
        // Verify callbacks are NOT supported for deleted plugins (falling back to using the default return).
5796
        $this->assertFalse(component_callback_exists('fake_fullfeatured', 'test_callback'));
5797
        $this->assertEquals('default', component_callback('fake_fullfeatured', 'test_callback', [], 'default'));
5798
        $this->assertEquals('default',
5799
            component_class_callback(\fake_fullfeatured\dummy::class, 'class_callback_test', [], 'default'));
5800
 
5801
        // Unset allversionshash to trigger a plugin functions rebuild, forcing it to pick up the injected mock plugin functions.
5802
        unset($CFG->allversionshash);
5803
        $plugins = get_plugins_with_function('test_callback');
5804
        $this->assertArrayNotHasKey('fake', $plugins);
5805
        $pluginlist = get_plugin_list_with_function('fake', 'test_callback');
5806
        $this->assertArrayNotHasKey('fake_fullfeatured', $pluginlist);
5807
    }
1 efrain 5808
}