Proyectos de Subversion Moodle

Rev

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