MDL-67673 phpunit: Remove deprecated assertContains() uses on strings
[moodle.git] / lib / tests / moodlelib_test.php
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/>.
17 /**
18  * Unit tests for (some of) ../moodlelib.php.
19  *
20  * @package    core
21  * @category   phpunit
22  * @copyright  &copy; 2006 The Open University
23  * @author     T.J.Hunt@open.ac.uk
24  * @author     nicolas@moodle.com
25  */
27 defined('MOODLE_INTERNAL') || die();
29 class core_moodlelib_testcase extends advanced_testcase {
31     public static $includecoverage = array('lib/moodlelib.php');
33     /**
34      * Define a local decimal separator.
35      *
36      * It is not possible to directly change the result of get_string in
37      * a unit test. Instead, we create a language pack for language 'xx' in
38      * dataroot and make langconfig.php with the string we need to change.
39      * The default example separator used here is 'X'; on PHP 5.3 and before this
40      * must be a single byte character due to PHP bug/limitation in
41      * number_format, so you can't use UTF-8 characters.
42      *
43      * @param string $decsep Separator character. Defaults to `'X'`.
44      */
45     protected function define_local_decimal_separator(string $decsep = 'X') {
46         global $SESSION, $CFG;
48         $SESSION->lang = 'xx';
49         $langconfig = "<?php\n\$string['decsep'] = '$decsep';";
50         $langfolder = $CFG->dataroot . '/lang/xx';
51         check_dir_exists($langfolder);
52         file_put_contents($langfolder . '/langconfig.php', $langconfig);
54         // Ensure the new value is picked up and not taken from the cache.
55         $stringmanager = get_string_manager();
56         $stringmanager->reset_caches(true);
57     }
59     public function test_cleanremoteaddr() {
60         // IPv4.
61         $this->assertNull(cleanremoteaddr('1023.121.234.1'));
62         $this->assertSame('123.121.234.1', cleanremoteaddr('123.121.234.01 '));
64         // IPv6.
65         $this->assertNull(cleanremoteaddr('0:0:0:0:0:0:0:0:0'));
66         $this->assertNull(cleanremoteaddr('0:0:0:0:0:0:0:abh'));
67         $this->assertNull(cleanremoteaddr('0:0:0:::0:0:1'));
68         $this->assertSame('::', cleanremoteaddr('0:0:0:0:0:0:0:0', true));
69         $this->assertSame('::1:1', cleanremoteaddr('0:0:0:0:0:0:1:1', true));
70         $this->assertSame('abcd:ef::', cleanremoteaddr('abcd:00ef:0:0:0:0:0:0', true));
71         $this->assertSame('1::1', cleanremoteaddr('1:0:0:0:0:0:0:1', true));
72         $this->assertSame('0:0:0:0:0:0:10:1', cleanremoteaddr('::10:1', false));
73         $this->assertSame('1:1:0:0:0:0:0:0', cleanremoteaddr('01:1::', false));
74         $this->assertSame('10:0:0:0:0:0:0:10', cleanremoteaddr('10::10', false));
75         $this->assertSame('::ffff:c0a8:11', cleanremoteaddr('::ffff:192.168.1.1', true));
76     }
78     public function test_address_in_subnet() {
79         // 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask).
80         $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.1/32'));
81         $this->assertFalse(address_in_subnet('123.121.23.1', '123.121.23.0/32'));
82         $this->assertTrue(address_in_subnet('10.10.10.100',  '123.121.23.45/0'));
83         $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.0/24'));
84         $this->assertFalse(address_in_subnet('123.121.34.1', '123.121.234.0/24'));
85         $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.0/30'));
86         $this->assertFalse(address_in_subnet('123.121.23.8', '123.121.23.0/30'));
87         $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba/128'));
88         $this->assertFalse(address_in_subnet('bab:baba::baba', 'bab:baba::cece/128'));
89         $this->assertTrue(address_in_subnet('baba:baba::baba', 'cece:cece::cece/0'));
90         $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba/128'));
91         $this->assertTrue(address_in_subnet('baba:baba::00ba', 'baba:baba::/120'));
92         $this->assertFalse(address_in_subnet('baba:baba::aba', 'baba:baba::/120'));
93         $this->assertTrue(address_in_subnet('baba::baba:00ba', 'baba::baba:0/112'));
94         $this->assertFalse(address_in_subnet('baba::aba:00ba', 'baba::baba:0/112'));
95         $this->assertFalse(address_in_subnet('aba::baba:0000', 'baba::baba:0/112'));
97         // Fixed input.
98         $this->assertTrue(address_in_subnet('123.121.23.1   ', ' 123.121.23.0 / 24'));
99         $this->assertTrue(address_in_subnet('::ffff:10.1.1.1', ' 0:0:0:000:0:ffff:a1:10 / 126'));
101         // Incorrect input.
102         $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/-2'));
103         $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/64'));
104         $this->assertFalse(address_in_subnet('123.121.234.x', '123.121.234.1/24'));
105         $this->assertFalse(address_in_subnet('123.121.234.0', '123.121.234.xx/24'));
106         $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/xx0'));
107         $this->assertFalse(address_in_subnet('::1', '::aa:0/xx0'));
108         $this->assertFalse(address_in_subnet('::1', '::aa:0/-5'));
109         $this->assertFalse(address_in_subnet('::1', '::aa:0/130'));
110         $this->assertFalse(address_in_subnet('x:1', '::aa:0/130'));
111         $this->assertFalse(address_in_subnet('::1', '::ax:0/130'));
113         // 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).
114         $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12-14'));
115         $this->assertTrue(address_in_subnet('123.121.234.13', '123.121.234.12-14'));
116         $this->assertTrue(address_in_subnet('123.121.234.14', '123.121.234.12-14'));
117         $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.12-14'));
118         $this->assertFalse(address_in_subnet('123.121.234.20', '123.121.234.12-14'));
119         $this->assertFalse(address_in_subnet('123.121.23.12', '123.121.234.12-14'));
120         $this->assertFalse(address_in_subnet('123.12.234.12', '123.121.234.12-14'));
121         $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba-babe'));
122         $this->assertTrue(address_in_subnet('baba:baba::babc', 'baba:baba::baba-babe'));
123         $this->assertTrue(address_in_subnet('baba:baba::babe', 'baba:baba::baba-babe'));
124         $this->assertFalse(address_in_subnet('bab:baba::bab0', 'bab:baba::baba-babe'));
125         $this->assertFalse(address_in_subnet('bab:baba::babf', 'bab:baba::baba-babe'));
126         $this->assertFalse(address_in_subnet('bab:baba::bfbe', 'bab:baba::baba-babe'));
127         $this->assertFalse(address_in_subnet('bfb:baba::babe', 'bab:baba::baba-babe'));
129         // Fixed input.
130         $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12 - 14 '));
131         $this->assertTrue(address_in_subnet('bab:baba::babe', 'bab:baba::baba - babe  '));
133         // Incorrect input.
134         $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12-234.14'));
135         $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12-256'));
136         $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12--256'));
138         // 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-).
139         $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12'));
140         $this->assertFalse(address_in_subnet('123.121.23.12', '123.121.23.13'));
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.234'));
143         $this->assertTrue(address_in_subnet('123.121.234.12', '123.121'));
144         $this->assertTrue(address_in_subnet('123.121.234.12', '123'));
145         $this->assertFalse(address_in_subnet('123.121.234.1', '12.121.234.'));
146         $this->assertFalse(address_in_subnet('123.121.234.1', '12.121.234'));
147         $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:baba::bab'));
148         $this->assertFalse(address_in_subnet('baba:baba::ba', 'baba:baba::bc'));
149         $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:baba'));
150         $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:'));
151         $this->assertFalse(address_in_subnet('bab:baba::bab', 'baba:'));
153         // Multiple subnets.
154         $this->assertTrue(address_in_subnet('123.121.234.12', '::1/64, 124., 123.121.234.10-30'));
155         $this->assertTrue(address_in_subnet('124.121.234.12', '::1/64, 124., 123.121.234.10-30'));
156         $this->assertTrue(address_in_subnet('::2',            '::1/64, 124., 123.121.234.10-30'));
157         $this->assertFalse(address_in_subnet('12.121.234.12', '::1/64, 124., 123.121.234.10-30'));
159         // Other incorrect input.
160         $this->assertFalse(address_in_subnet('123.123.123.123', ''));
161     }
163     public function test_fix_utf8() {
164         // Make sure valid data including other types is not changed.
165         $this->assertSame(null, fix_utf8(null));
166         $this->assertSame(1, fix_utf8(1));
167         $this->assertSame(1.1, fix_utf8(1.1));
168         $this->assertSame(true, fix_utf8(true));
169         $this->assertSame('', fix_utf8(''));
170         $this->assertSame('abc', fix_utf8('abc'));
171         $array = array('do', 're', 'mi');
172         $this->assertSame($array, fix_utf8($array));
173         $object = new stdClass();
174         $object->a = 'aa';
175         $object->b = 'bb';
176         $this->assertEquals($object, fix_utf8($object));
178         // valid utf8 string
179         $this->assertSame("žlutý koníček přeskočil potůček \n\t\r", fix_utf8("žlutý koníček přeskočil potůček \n\t\r\0"));
181         // Invalid utf8 string.
182         $this->assertSame('aš', fix_utf8('a'.chr(130).'š'), 'This fails with buggy iconv() when mbstring extenstion is not available as fallback.');
183     }
185     public function test_optional_param() {
186         global $CFG;
188         $_POST['username'] = 'post_user';
189         $_GET['username'] = 'get_user';
190         $this->assertSame($_POST['username'], optional_param('username', 'default_user', PARAM_RAW));
192         unset($_POST['username']);
193         $this->assertSame($_GET['username'], optional_param('username', 'default_user', PARAM_RAW));
195         unset($_GET['username']);
196         $this->assertSame('default_user', optional_param('username', 'default_user', PARAM_RAW));
198         // Make sure exception is triggered when some params are missing, hide error notices here - new in 2.2.
199         $_POST['username'] = 'post_user';
200         try {
201             optional_param('username', 'default_user', null);
202             $this->fail('coding_exception expected');
203         } catch (moodle_exception $ex) {
204             $this->assertInstanceOf('coding_exception', $ex);
205         }
206         try {
207             @optional_param('username', 'default_user');
208             $this->fail('coding_exception expected');
209         } catch (moodle_exception $ex) {
210             $this->assertInstanceOf('coding_exception', $ex);
211         } catch (Error $error) {
212             // PHP 7.1 throws Error even earlier.
213             $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
214         }
215         try {
216             @optional_param('username');
217             $this->fail('coding_exception expected');
218         } catch (moodle_exception $ex) {
219             $this->assertInstanceOf('coding_exception', $ex);
220         } catch (Error $error) {
221             // PHP 7.1 throws Error even earlier.
222             $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
223         }
224         try {
225             optional_param('', 'default_user', PARAM_RAW);
226             $this->fail('coding_exception expected');
227         } catch (moodle_exception $ex) {
228             $this->assertInstanceOf('coding_exception', $ex);
229         }
231         // Make sure warning is displayed if array submitted - TODO: throw exception in Moodle 2.3.
232         $_POST['username'] = array('a'=>'a');
233         $this->assertSame($_POST['username'], optional_param('username', 'default_user', PARAM_RAW));
234         $this->assertDebuggingCalled();
235     }
237     public function test_optional_param_array() {
238         global $CFG;
240         $_POST['username'] = array('a'=>'post_user');
241         $_GET['username'] = array('a'=>'get_user');
242         $this->assertSame($_POST['username'], optional_param_array('username', array('a'=>'default_user'), PARAM_RAW));
244         unset($_POST['username']);
245         $this->assertSame($_GET['username'], optional_param_array('username', array('a'=>'default_user'), PARAM_RAW));
247         unset($_GET['username']);
248         $this->assertSame(array('a'=>'default_user'), optional_param_array('username', array('a'=>'default_user'), PARAM_RAW));
250         // Make sure exception is triggered when some params are missing, hide error notices here - new in 2.2.
251         $_POST['username'] = array('a'=>'post_user');
252         try {
253             optional_param_array('username', array('a'=>'default_user'), null);
254             $this->fail('coding_exception expected');
255         } catch (moodle_exception $ex) {
256             $this->assertInstanceOf('coding_exception', $ex);
257         }
258         try {
259             @optional_param_array('username', array('a'=>'default_user'));
260             $this->fail('coding_exception expected');
261         } catch (moodle_exception $ex) {
262             $this->assertInstanceOf('coding_exception', $ex);
263         } catch (Error $error) {
264             // PHP 7.1 throws Error even earlier.
265             $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
266         }
267         try {
268             @optional_param_array('username');
269             $this->fail('coding_exception expected');
270         } catch (moodle_exception $ex) {
271             $this->assertInstanceOf('coding_exception', $ex);
272         } catch (Error $error) {
273             // PHP 7.1 throws Error even earlier.
274             $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
275         }
276         try {
277             optional_param_array('', array('a'=>'default_user'), PARAM_RAW);
278             $this->fail('coding_exception expected');
279         } catch (moodle_exception $ex) {
280             $this->assertInstanceOf('coding_exception', $ex);
281         }
283         // Do not allow nested arrays.
284         try {
285             $_POST['username'] = array('a'=>array('b'=>'post_user'));
286             optional_param_array('username', array('a'=>'default_user'), PARAM_RAW);
287             $this->fail('coding_exception expected');
288         } catch (coding_exception $ex) {
289             $this->assertTrue(true);
290         }
292         // Do not allow non-arrays.
293         $_POST['username'] = 'post_user';
294         $this->assertSame(array('a'=>'default_user'), optional_param_array('username', array('a'=>'default_user'), PARAM_RAW));
295         $this->assertDebuggingCalled();
297         // Make sure array keys are sanitised.
298         $_POST['username'] = array('abc123_;-/*-+ '=>'arrggh', 'a1_-'=>'post_user');
299         $this->assertSame(array('a1_-'=>'post_user'), optional_param_array('username', array(), PARAM_RAW));
300         $this->assertDebuggingCalled();
301     }
303     public function test_required_param() {
304         $_POST['username'] = 'post_user';
305         $_GET['username'] = 'get_user';
306         $this->assertSame('post_user', required_param('username', PARAM_RAW));
308         unset($_POST['username']);
309         $this->assertSame('get_user', required_param('username', PARAM_RAW));
311         unset($_GET['username']);
312         try {
313             $this->assertSame('default_user', required_param('username', PARAM_RAW));
314             $this->fail('moodle_exception expected');
315         } catch (moodle_exception $ex) {
316             $this->assertInstanceOf('moodle_exception', $ex);
317         }
319         // Make sure exception is triggered when some params are missing, hide error notices here - new in 2.2.
320         $_POST['username'] = 'post_user';
321         try {
322             @required_param('username');
323             $this->fail('coding_exception expected');
324         } catch (moodle_exception $ex) {
325             $this->assertInstanceOf('coding_exception', $ex);
326         } catch (Error $error) {
327             // PHP 7.1 throws Error even earlier.
328             $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
329         }
330         try {
331             required_param('username', '');
332             $this->fail('coding_exception expected');
333         } catch (moodle_exception $ex) {
334             $this->assertInstanceOf('coding_exception', $ex);
335         }
336         try {
337             required_param('', PARAM_RAW);
338             $this->fail('coding_exception expected');
339         } catch (moodle_exception $ex) {
340             $this->assertInstanceOf('coding_exception', $ex);
341         }
343         // Make sure warning is displayed if array submitted - TODO: throw exception in Moodle 2.3.
344         $_POST['username'] = array('a'=>'a');
345         $this->assertSame($_POST['username'], required_param('username', PARAM_RAW));
346         $this->assertDebuggingCalled();
347     }
349     public function test_required_param_array() {
350         global $CFG;
352         $_POST['username'] = array('a'=>'post_user');
353         $_GET['username'] = array('a'=>'get_user');
354         $this->assertSame($_POST['username'], required_param_array('username', PARAM_RAW));
356         unset($_POST['username']);
357         $this->assertSame($_GET['username'], required_param_array('username', PARAM_RAW));
359         // Make sure exception is triggered when some params are missing, hide error notices here - new in 2.2.
360         $_POST['username'] = array('a'=>'post_user');
361         try {
362             required_param_array('username', null);
363             $this->fail('coding_exception expected');
364         } catch (moodle_exception $ex) {
365             $this->assertInstanceOf('coding_exception', $ex);
366         }
367         try {
368             @required_param_array('username');
369             $this->fail('coding_exception expected');
370         } catch (moodle_exception $ex) {
371             $this->assertInstanceOf('coding_exception', $ex);
372         } catch (Error $error) {
373             // PHP 7.1 throws Error.
374             $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
375         }
376         try {
377             required_param_array('', PARAM_RAW);
378             $this->fail('coding_exception expected');
379         } catch (moodle_exception $ex) {
380             $this->assertInstanceOf('coding_exception', $ex);
381         }
383         // Do not allow nested arrays.
384         try {
385             $_POST['username'] = array('a'=>array('b'=>'post_user'));
386             required_param_array('username', PARAM_RAW);
387             $this->fail('coding_exception expected');
388         } catch (moodle_exception $ex) {
389             $this->assertInstanceOf('coding_exception', $ex);
390         }
392         // Do not allow non-arrays.
393         try {
394             $_POST['username'] = 'post_user';
395             required_param_array('username', PARAM_RAW);
396             $this->fail('moodle_exception expected');
397         } catch (moodle_exception $ex) {
398             $this->assertInstanceOf('moodle_exception', $ex);
399         }
401         // Make sure array keys are sanitised.
402         $_POST['username'] = array('abc123_;-/*-+ '=>'arrggh', 'a1_-'=>'post_user');
403         $this->assertSame(array('a1_-'=>'post_user'), required_param_array('username', PARAM_RAW));
404         $this->assertDebuggingCalled();
405     }
407     public function test_clean_param() {
408         // Forbid objects and arrays.
409         try {
410             clean_param(array('x', 'y'), PARAM_RAW);
411             $this->fail('coding_exception expected');
412         } catch (moodle_exception $ex) {
413             $this->assertInstanceOf('coding_exception', $ex);
414         }
415         try {
416             $param = new stdClass();
417             $param->id = 1;
418             clean_param($param, PARAM_RAW);
419             $this->fail('coding_exception expected');
420         } catch (moodle_exception $ex) {
421             $this->assertInstanceOf('coding_exception', $ex);
422         }
424         // Require correct type.
425         try {
426             clean_param('x', 'xxxxxx');
427             $this->fail('moodle_exception expected');
428         } catch (moodle_exception $ex) {
429             $this->assertInstanceOf('moodle_exception', $ex);
430         }
431         try {
432             @clean_param('x');
433             $this->fail('moodle_exception expected');
434         } catch (moodle_exception $ex) {
435             $this->assertInstanceOf('moodle_exception', $ex);
436         } catch (Error $error) {
437             // PHP 7.1 throws Error even earlier.
438             $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
439         }
440     }
442     public function test_clean_param_array() {
443         $this->assertSame(array(), clean_param_array(null, PARAM_RAW));
444         $this->assertSame(array('a', 'b'), clean_param_array(array('a', 'b'), PARAM_RAW));
445         $this->assertSame(array('a', array('b')), clean_param_array(array('a', array('b')), PARAM_RAW, true));
447         // Require correct type.
448         try {
449             clean_param_array(array('x'), 'xxxxxx');
450             $this->fail('moodle_exception expected');
451         } catch (moodle_exception $ex) {
452             $this->assertInstanceOf('moodle_exception', $ex);
453         }
454         try {
455             @clean_param_array(array('x'));
456             $this->fail('moodle_exception expected');
457         } catch (moodle_exception $ex) {
458             $this->assertInstanceOf('moodle_exception', $ex);
459         } catch (Error $error) {
460             // PHP 7.1 throws Error even earlier.
461             $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
462         }
464         try {
465             clean_param_array(array('x', array('y')), PARAM_RAW);
466             $this->fail('coding_exception expected');
467         } catch (moodle_exception $ex) {
468             $this->assertInstanceOf('coding_exception', $ex);
469         }
471         // Test recursive.
472     }
474     public function test_clean_param_raw() {
475         $this->assertSame(
476             '#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)',
477             clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_RAW));
478     }
480     public function test_clean_param_trim() {
481         $this->assertSame('Frog toad', clean_param("   Frog toad   \r\n  ", PARAM_RAW_TRIMMED));
482     }
484     public function test_clean_param_clean() {
485         // PARAM_CLEAN is an ugly hack, do not use in new code (skodak),
486         // instead use more specific type, or submit sothing that can be verified properly.
487         $this->assertSame('xx', clean_param('xx<script>', PARAM_CLEAN));
488     }
490     public function test_clean_param_alpha() {
491         $this->assertSame('DSFMOSDJ', clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_ALPHA));
492     }
494     public function test_clean_param_alphanum() {
495         $this->assertSame('978942897DSFMOSDJ', clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_ALPHANUM));
496     }
498     public function test_clean_param_alphaext() {
499         $this->assertSame('DSFMOSDJ', clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_ALPHAEXT));
500     }
502     public function test_clean_param_sequence() {
503         $this->assertSame(',9789,42897', clean_param('#()*#,9789\'".,<42897></?$(*DSFMO#$*)(SDJ)($*)', PARAM_SEQUENCE));
504     }
506     public function test_clean_param_component() {
507         // Please note the cleaning of component names is very strict, no guessing here.
508         $this->assertSame('mod_forum', clean_param('mod_forum', PARAM_COMPONENT));
509         $this->assertSame('block_online_users', clean_param('block_online_users', PARAM_COMPONENT));
510         $this->assertSame('block_blond_online_users', clean_param('block_blond_online_users', PARAM_COMPONENT));
511         $this->assertSame('mod_something2', clean_param('mod_something2', PARAM_COMPONENT));
512         $this->assertSame('forum', clean_param('forum', PARAM_COMPONENT));
513         $this->assertSame('user', clean_param('user', PARAM_COMPONENT));
514         $this->assertSame('rating', clean_param('rating', PARAM_COMPONENT));
515         $this->assertSame('feedback360', clean_param('feedback360', PARAM_COMPONENT));
516         $this->assertSame('mod_feedback360', clean_param('mod_feedback360', PARAM_COMPONENT));
517         $this->assertSame('', clean_param('mod_2something', PARAM_COMPONENT));
518         $this->assertSame('', clean_param('2mod_something', PARAM_COMPONENT));
519         $this->assertSame('', clean_param('mod_something_xx', PARAM_COMPONENT));
520         $this->assertSame('', clean_param('auth_something__xx', PARAM_COMPONENT));
521         $this->assertSame('', clean_param('mod_Something', PARAM_COMPONENT));
522         $this->assertSame('', clean_param('mod_somethíng', PARAM_COMPONENT));
523         $this->assertSame('', clean_param('mod__something', PARAM_COMPONENT));
524         $this->assertSame('', clean_param('auth_xx-yy', PARAM_COMPONENT));
525         $this->assertSame('', clean_param('_auth_xx', PARAM_COMPONENT));
526         $this->assertSame('a2uth_xx', clean_param('a2uth_xx', PARAM_COMPONENT));
527         $this->assertSame('', clean_param('auth_xx_', PARAM_COMPONENT));
528         $this->assertSame('', clean_param('auth_xx.old', PARAM_COMPONENT));
529         $this->assertSame('', clean_param('_user', PARAM_COMPONENT));
530         $this->assertSame('', clean_param('2rating', PARAM_COMPONENT));
531         $this->assertSame('', clean_param('user_', PARAM_COMPONENT));
532     }
534     public function test_clean_param_localisedfloat() {
536         $this->assertSame(0.5, clean_param('0.5', PARAM_LOCALISEDFLOAT));
537         $this->assertSame(false, clean_param('0X5', PARAM_LOCALISEDFLOAT));
538         $this->assertSame(0.5, clean_param('.5', PARAM_LOCALISEDFLOAT));
539         $this->assertSame(false, clean_param('X5', PARAM_LOCALISEDFLOAT));
540         $this->assertSame(10.5, clean_param('10.5', PARAM_LOCALISEDFLOAT));
541         $this->assertSame(false, clean_param('10X5', PARAM_LOCALISEDFLOAT));
542         $this->assertSame(1000.5, clean_param('1 000.5', PARAM_LOCALISEDFLOAT));
543         $this->assertSame(false, clean_param('1 000X5', PARAM_LOCALISEDFLOAT));
544         $this->assertSame(false, clean_param('1.000.5', PARAM_LOCALISEDFLOAT));
545         $this->assertSame(false, clean_param('1X000X5', PARAM_LOCALISEDFLOAT));
546         $this->assertSame(false, clean_param('nan', PARAM_LOCALISEDFLOAT));
547         $this->assertSame(false, clean_param('10.6blah', PARAM_LOCALISEDFLOAT));
549         // Tests with a localised decimal separator.
550         $this->define_local_decimal_separator();
552         $this->assertSame(0.5, clean_param('0.5', PARAM_LOCALISEDFLOAT));
553         $this->assertSame(0.5, clean_param('0X5', PARAM_LOCALISEDFLOAT));
554         $this->assertSame(0.5, clean_param('.5', PARAM_LOCALISEDFLOAT));
555         $this->assertSame(0.5, clean_param('X5', PARAM_LOCALISEDFLOAT));
556         $this->assertSame(10.5, clean_param('10.5', PARAM_LOCALISEDFLOAT));
557         $this->assertSame(10.5, clean_param('10X5', PARAM_LOCALISEDFLOAT));
558         $this->assertSame(1000.5, clean_param('1 000.5', PARAM_LOCALISEDFLOAT));
559         $this->assertSame(1000.5, clean_param('1 000X5', PARAM_LOCALISEDFLOAT));
560         $this->assertSame(false, clean_param('1.000.5', PARAM_LOCALISEDFLOAT));
561         $this->assertSame(false, clean_param('1X000X5', PARAM_LOCALISEDFLOAT));
562         $this->assertSame(false, clean_param('nan', PARAM_LOCALISEDFLOAT));
563         $this->assertSame(false, clean_param('10X6blah', PARAM_LOCALISEDFLOAT));
564     }
566     public function test_is_valid_plugin_name() {
567         $this->assertTrue(is_valid_plugin_name('forum'));
568         $this->assertTrue(is_valid_plugin_name('forum2'));
569         $this->assertTrue(is_valid_plugin_name('feedback360'));
570         $this->assertTrue(is_valid_plugin_name('online_users'));
571         $this->assertTrue(is_valid_plugin_name('blond_online_users'));
572         $this->assertFalse(is_valid_plugin_name('online__users'));
573         $this->assertFalse(is_valid_plugin_name('forum '));
574         $this->assertFalse(is_valid_plugin_name('forum.old'));
575         $this->assertFalse(is_valid_plugin_name('xx-yy'));
576         $this->assertFalse(is_valid_plugin_name('2xx'));
577         $this->assertFalse(is_valid_plugin_name('Xx'));
578         $this->assertFalse(is_valid_plugin_name('_xx'));
579         $this->assertFalse(is_valid_plugin_name('xx_'));
580     }
582     public function test_clean_param_plugin() {
583         // Please note the cleaning of plugin names is very strict, no guessing here.
584         $this->assertSame('forum', clean_param('forum', PARAM_PLUGIN));
585         $this->assertSame('forum2', clean_param('forum2', PARAM_PLUGIN));
586         $this->assertSame('feedback360', clean_param('feedback360', PARAM_PLUGIN));
587         $this->assertSame('online_users', clean_param('online_users', PARAM_PLUGIN));
588         $this->assertSame('blond_online_users', clean_param('blond_online_users', PARAM_PLUGIN));
589         $this->assertSame('', clean_param('online__users', PARAM_PLUGIN));
590         $this->assertSame('', clean_param('forum ', PARAM_PLUGIN));
591         $this->assertSame('', clean_param('forum.old', PARAM_PLUGIN));
592         $this->assertSame('', clean_param('xx-yy', PARAM_PLUGIN));
593         $this->assertSame('', clean_param('2xx', PARAM_PLUGIN));
594         $this->assertSame('', clean_param('Xx', PARAM_PLUGIN));
595         $this->assertSame('', clean_param('_xx', PARAM_PLUGIN));
596         $this->assertSame('', clean_param('xx_', PARAM_PLUGIN));
597     }
599     public function test_clean_param_area() {
600         // Please note the cleaning of area names is very strict, no guessing here.
601         $this->assertSame('something', clean_param('something', PARAM_AREA));
602         $this->assertSame('something2', clean_param('something2', PARAM_AREA));
603         $this->assertSame('some_thing', clean_param('some_thing', PARAM_AREA));
604         $this->assertSame('some_thing_xx', clean_param('some_thing_xx', PARAM_AREA));
605         $this->assertSame('feedback360', clean_param('feedback360', PARAM_AREA));
606         $this->assertSame('', clean_param('_something', PARAM_AREA));
607         $this->assertSame('', clean_param('something_', PARAM_AREA));
608         $this->assertSame('', clean_param('2something', PARAM_AREA));
609         $this->assertSame('', clean_param('Something', PARAM_AREA));
610         $this->assertSame('', clean_param('some-thing', PARAM_AREA));
611         $this->assertSame('', clean_param('somethííng', PARAM_AREA));
612         $this->assertSame('', clean_param('something.x', PARAM_AREA));
613     }
615     public function test_clean_param_text() {
616         $this->assertSame(PARAM_TEXT, PARAM_MULTILANG);
617         // Standard.
618         $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));
619         $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));
620         $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));
621         // Malformed.
622         $this->assertSame('<span lang="en" class="multilang">aa</span>', clean_param('<span lang="en" class="multilang">aa</span>', PARAM_TEXT));
623         $this->assertSame('aa', clean_param('<span lang="en" class="nothing" class="multilang">aa</span>', PARAM_TEXT));
624         $this->assertSame('aa', clean_param('<lang lang="en" class="multilang">aa</lang>', PARAM_TEXT));
625         $this->assertSame('aa', clean_param('<lang lang="en!!">aa</lang>', PARAM_TEXT));
626         $this->assertSame('aa', clean_param('<span lang="en==" class="multilang">aa</span>', PARAM_TEXT));
627         $this->assertSame('abc', clean_param('a<em>b</em>c', PARAM_TEXT));
628         $this->assertSame('a>c>', clean_param('a><xx >c>', PARAM_TEXT)); // Standard strip_tags() behaviour.
629         $this->assertSame('a', clean_param('a<b', PARAM_TEXT));
630         $this->assertSame('a>b', clean_param('a>b', PARAM_TEXT));
631         $this->assertSame('<lang lang="en">a>a</lang>', clean_param('<lang lang="en">a>a</lang>', PARAM_TEXT)); // Standard strip_tags() behaviour.
632         $this->assertSame('a', clean_param('<lang lang="en">a<a</lang>', PARAM_TEXT));
633         $this->assertSame('<lang lang="en">aa</lang>', clean_param('<lang lang="en">a<br>a</lang>', PARAM_TEXT));
634     }
636     public function test_clean_param_url() {
637         // Test PARAM_URL and PARAM_LOCALURL a bit.
638         // Valid URLs.
639         $this->assertSame('http://google.com/', clean_param('http://google.com/', PARAM_URL));
640         $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));
641         $this->assertSame('http://localhost/', clean_param('http://localhost/', PARAM_URL));
642         $this->assertSame('http://0.255.1.1/numericip.php', clean_param('http://0.255.1.1/numericip.php', PARAM_URL));
643         $this->assertSame('https://google.com/', clean_param('https://google.com/', PARAM_URL));
644         $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));
645         $this->assertSame('https://localhost/', clean_param('https://localhost/', PARAM_URL));
646         $this->assertSame('https://0.255.1.1/numericip.php', clean_param('https://0.255.1.1/numericip.php', PARAM_URL));
647         $this->assertSame('ftp://ftp.debian.org/debian/', clean_param('ftp://ftp.debian.org/debian/', PARAM_URL));
648         $this->assertSame('/just/a/path', clean_param('/just/a/path', PARAM_URL));
649         // Invalid URLs.
650         $this->assertSame('', clean_param('funny:thing', PARAM_URL));
651         $this->assertSame('', clean_param('http://example.ee/sdsf"f', PARAM_URL));
652         $this->assertSame('', clean_param('javascript://comment%0Aalert(1)', PARAM_URL));
653         $this->assertSame('', clean_param('rtmp://example.com/livestream', PARAM_URL));
654         $this->assertSame('', clean_param('rtmp://example.com/live&foo', PARAM_URL));
655         $this->assertSame('', clean_param('rtmp://example.com/fms&mp4:path/to/file.mp4', PARAM_URL));
656         $this->assertSame('', clean_param('mailto:support@moodle.org', PARAM_URL));
657         $this->assertSame('', clean_param('mailto:support@moodle.org?subject=Hello%20Moodle', PARAM_URL));
658         $this->assertSame('', clean_param('mailto:support@moodle.org?subject=Hello%20Moodle&cc=feedback@moodle.org', PARAM_URL));
659     }
661     public function test_clean_param_localurl() {
662         global $CFG;
664         $this->resetAfterTest();
666         // External, invalid.
667         $this->assertSame('', clean_param('funny:thing', PARAM_LOCALURL));
668         $this->assertSame('', clean_param('http://google.com/', PARAM_LOCALURL));
669         $this->assertSame('', clean_param('https://google.com/?test=true', PARAM_LOCALURL));
670         $this->assertSame('', clean_param('http://some.very.long.and.silly.domain/with/a/path/', PARAM_LOCALURL));
672         // Local absolute.
673         $this->assertSame(clean_param($CFG->wwwroot, PARAM_LOCALURL), $CFG->wwwroot);
674         $this->assertSame($CFG->wwwroot . '/with/something?else=true',
675             clean_param($CFG->wwwroot . '/with/something?else=true', PARAM_LOCALURL));
677         // Local relative.
678         $this->assertSame('/just/a/path', clean_param('/just/a/path', PARAM_LOCALURL));
679         $this->assertSame('course/view.php?id=3', clean_param('course/view.php?id=3', PARAM_LOCALURL));
681         // Local absolute HTTPS in a non HTTPS site.
682         $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot); // Need to simulate non-https site.
683         $httpsroot = str_replace('http:', 'https:', $CFG->wwwroot);
684         $this->assertSame('', clean_param($httpsroot, PARAM_LOCALURL));
685         $this->assertSame('', clean_param($httpsroot . '/with/something?else=true', PARAM_LOCALURL));
687         // Local absolute HTTPS in a HTTPS site.
688         $CFG->wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
689         $httpsroot = $CFG->wwwroot;
690         $this->assertSame($httpsroot, clean_param($httpsroot, PARAM_LOCALURL));
691         $this->assertSame($httpsroot . '/with/something?else=true',
692             clean_param($httpsroot . '/with/something?else=true', PARAM_LOCALURL));
694         // Test open redirects are not possible.
695         $CFG->wwwroot = 'http://www.example.com';
696         $this->assertSame('', clean_param('http://www.example.com.evil.net/hack.php', PARAM_LOCALURL));
697         $CFG->wwwroot = 'https://www.example.com';
698         $this->assertSame('', clean_param('https://www.example.com.evil.net/hack.php', PARAM_LOCALURL));
699     }
701     public function test_clean_param_file() {
702         $this->assertSame('correctfile.txt', clean_param('correctfile.txt', PARAM_FILE));
703         $this->assertSame('badfile.txt', clean_param('b\'a<d`\\/fi:l>e.t"x|t', PARAM_FILE));
704         $this->assertSame('..parentdirfile.txt', clean_param('../parentdirfile.txt', PARAM_FILE));
705         $this->assertSame('....grandparentdirfile.txt', clean_param('../../grandparentdirfile.txt', PARAM_FILE));
706         $this->assertSame('..winparentdirfile.txt', clean_param('..\winparentdirfile.txt', PARAM_FILE));
707         $this->assertSame('....wingrandparentdir.txt', clean_param('..\..\wingrandparentdir.txt', PARAM_FILE));
708         $this->assertSame('myfile.a.b.txt', clean_param('myfile.a.b.txt', PARAM_FILE));
709         $this->assertSame('myfile..a..b.txt', clean_param('myfile..a..b.txt', PARAM_FILE));
710         $this->assertSame('myfile.a..b...txt', clean_param('myfile.a..b...txt', PARAM_FILE));
711         $this->assertSame('myfile.a.txt', clean_param('myfile.a.txt', PARAM_FILE));
712         $this->assertSame('myfile...txt', clean_param('myfile...txt', PARAM_FILE));
713         $this->assertSame('...jpg', clean_param('...jpg', PARAM_FILE));
714         $this->assertSame('.a.b.', clean_param('.a.b.', PARAM_FILE));
715         $this->assertSame('', clean_param('.', PARAM_FILE));
716         $this->assertSame('', clean_param('..', PARAM_FILE));
717         $this->assertSame('...', clean_param('...', PARAM_FILE));
718         $this->assertSame('. . . .', clean_param('. . . .', PARAM_FILE));
719         $this->assertSame('dontrtrim.me. .. .. . ', clean_param('dontrtrim.me. .. .. . ', PARAM_FILE));
720         $this->assertSame(' . .dontltrim.me', clean_param(' . .dontltrim.me', PARAM_FILE));
721         $this->assertSame('here is a tab.txt', clean_param("here is a tab\t.txt", PARAM_FILE));
722         $this->assertSame('here is a linebreak.txt', clean_param("here is a line\r\nbreak.txt", PARAM_FILE));
724         // The following behaviours have been maintained although they seem a little odd.
725         $this->assertSame('funnything', clean_param('funny:thing', PARAM_FILE));
726         $this->assertSame('.currentdirfile.txt', clean_param('./currentdirfile.txt', PARAM_FILE));
727         $this->assertSame('ctempwindowsfile.txt', clean_param('c:\temp\windowsfile.txt', PARAM_FILE));
728         $this->assertSame('homeuserlinuxfile.txt', clean_param('/home/user/linuxfile.txt', PARAM_FILE));
729         $this->assertSame('~myfile.txt', clean_param('~/myfile.txt', PARAM_FILE));
730     }
732     public function test_clean_param_path() {
733         $this->assertSame('correctfile.txt', clean_param('correctfile.txt', PARAM_PATH));
734         $this->assertSame('bad/file.txt', clean_param('b\'a<d`\\/fi:l>e.t"x|t', PARAM_PATH));
735         $this->assertSame('/parentdirfile.txt', clean_param('../parentdirfile.txt', PARAM_PATH));
736         $this->assertSame('/grandparentdirfile.txt', clean_param('../../grandparentdirfile.txt', PARAM_PATH));
737         $this->assertSame('/winparentdirfile.txt', clean_param('..\winparentdirfile.txt', PARAM_PATH));
738         $this->assertSame('/wingrandparentdir.txt', clean_param('..\..\wingrandparentdir.txt', PARAM_PATH));
739         $this->assertSame('funnything', clean_param('funny:thing', PARAM_PATH));
740         $this->assertSame('./here', clean_param('./././here', PARAM_PATH));
741         $this->assertSame('./currentdirfile.txt', clean_param('./currentdirfile.txt', PARAM_PATH));
742         $this->assertSame('c/temp/windowsfile.txt', clean_param('c:\temp\windowsfile.txt', PARAM_PATH));
743         $this->assertSame('/home/user/linuxfile.txt', clean_param('/home/user/linuxfile.txt', PARAM_PATH));
744         $this->assertSame('/home../user ./.linuxfile.txt', clean_param('/home../user ./.linuxfile.txt', PARAM_PATH));
745         $this->assertSame('~/myfile.txt', clean_param('~/myfile.txt', PARAM_PATH));
746         $this->assertSame('~/myfile.txt', clean_param('~/../myfile.txt', PARAM_PATH));
747         $this->assertSame('/..b../.../myfile.txt', clean_param('/..b../.../myfile.txt', PARAM_PATH));
748         $this->assertSame('..b../.../myfile.txt', clean_param('..b../.../myfile.txt', PARAM_PATH));
749         $this->assertSame('/super/slashes/', clean_param('/super//slashes///', PARAM_PATH));
750     }
752     public function test_clean_param_username() {
753         global $CFG;
754         $currentstatus =  $CFG->extendedusernamechars;
756         // Run tests with extended character == false;.
757         $CFG->extendedusernamechars = false;
758         $this->assertSame('johndoe123', clean_param('johndoe123', PARAM_USERNAME) );
759         $this->assertSame('john.doe', clean_param('john.doe', PARAM_USERNAME));
760         $this->assertSame('john-doe', clean_param('john-doe', PARAM_USERNAME));
761         $this->assertSame('john-doe', clean_param('john- doe', PARAM_USERNAME));
762         $this->assertSame('john_doe', clean_param('john_doe', PARAM_USERNAME));
763         $this->assertSame('john@doe', clean_param('john@doe', PARAM_USERNAME));
764         $this->assertSame('johndoe', clean_param('john~doe', PARAM_USERNAME));
765         $this->assertSame('johndoe', clean_param('john´doe', PARAM_USERNAME));
766         $this->assertSame(clean_param('john# $%&()+_^', PARAM_USERNAME), 'john_');
767         $this->assertSame(clean_param(' john# $%&()+_^ ', PARAM_USERNAME), 'john_');
768         $this->assertSame(clean_param('john#$%&() ', PARAM_USERNAME), 'john');
769         $this->assertSame('johnd', clean_param('JOHNdóé ', PARAM_USERNAME));
770         $this->assertSame(clean_param('john.,:;-_/|\ñÑ[]A_X-,D {} ~!@#$%^&*()_+ ?><[] ščřžžý ?ýá\9e?\9eý??\9adoe ', PARAM_USERNAME), 'john.-_a_x-d@_doe');
772         // Test success condition, if extendedusernamechars == ENABLE;.
773         $CFG->extendedusernamechars = true;
774         $this->assertSame('john_doe', clean_param('john_doe', PARAM_USERNAME));
775         $this->assertSame('john@doe', clean_param('john@doe', PARAM_USERNAME));
776         $this->assertSame(clean_param('john# $%&()+_^', PARAM_USERNAME), 'john# $%&()+_^');
777         $this->assertSame(clean_param(' john# $%&()+_^ ', PARAM_USERNAME), 'john# $%&()+_^');
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('johndoe', clean_param('johnDOE', PARAM_USERNAME));
781         $this->assertSame('johndóé', clean_param('johndóé ', PARAM_USERNAME));
783         $CFG->extendedusernamechars = $currentstatus;
784     }
786     public function test_clean_param_stringid() {
787         // Test string identifiers validation.
788         // Valid strings.
789         $this->assertSame('validstring', clean_param('validstring', PARAM_STRINGID));
790         $this->assertSame('mod/foobar:valid_capability', clean_param('mod/foobar:valid_capability', PARAM_STRINGID));
791         $this->assertSame('CZ', clean_param('CZ', PARAM_STRINGID));
792         $this->assertSame('application/vnd.ms-powerpoint', clean_param('application/vnd.ms-powerpoint', PARAM_STRINGID));
793         $this->assertSame('grade2', clean_param('grade2', PARAM_STRINGID));
794         // Invalid strings.
795         $this->assertSame('', clean_param('trailing ', PARAM_STRINGID));
796         $this->assertSame('', clean_param('space bar', PARAM_STRINGID));
797         $this->assertSame('', clean_param('0numeric', PARAM_STRINGID));
798         $this->assertSame('', clean_param('*', PARAM_STRINGID));
799         $this->assertSame('', clean_param(' ', PARAM_STRINGID));
800     }
802     public function test_clean_param_timezone() {
803         // Test timezone validation.
804         $testvalues = array (
805             'America/Jamaica'                => 'America/Jamaica',
806             'America/Argentina/Cordoba'      => 'America/Argentina/Cordoba',
807             'America/Port-au-Prince'         => 'America/Port-au-Prince',
808             'America/Argentina/Buenos_Aires' => 'America/Argentina/Buenos_Aires',
809             'PST8PDT'                        => 'PST8PDT',
810             'Wrong.Value'                    => '',
811             'Wrong/.Value'                   => '',
812             'Wrong(Value)'                   => '',
813             '0'                              => '0',
814             '0.0'                            => '0.0',
815             '0.5'                            => '0.5',
816             '9.0'                            => '9.0',
817             '-9.0'                           => '-9.0',
818             '+9.0'                           => '+9.0',
819             '9.5'                            => '9.5',
820             '-9.5'                           => '-9.5',
821             '+9.5'                           => '+9.5',
822             '12.0'                           => '12.0',
823             '-12.0'                          => '-12.0',
824             '+12.0'                          => '+12.0',
825             '12.5'                           => '12.5',
826             '-12.5'                          => '-12.5',
827             '+12.5'                          => '+12.5',
828             '13.0'                           => '13.0',
829             '-13.0'                          => '-13.0',
830             '+13.0'                          => '+13.0',
831             '13.5'                           => '',
832             '+13.5'                          => '',
833             '-13.5'                          => '',
834             '0.2'                            => '');
836         foreach ($testvalues as $testvalue => $expectedvalue) {
837             $actualvalue = clean_param($testvalue, PARAM_TIMEZONE);
838             $this->assertEquals($expectedvalue, $actualvalue);
839         }
840     }
842     public function test_validate_param() {
843         try {
844             $param = validate_param('11a', PARAM_INT);
845             $this->fail('invalid_parameter_exception expected');
846         } catch (moodle_exception $ex) {
847             $this->assertInstanceOf('invalid_parameter_exception', $ex);
848         }
850         $param = validate_param('11', PARAM_INT);
851         $this->assertSame(11, $param);
853         try {
854             $param = validate_param(null, PARAM_INT, false);
855             $this->fail('invalid_parameter_exception expected');
856         } catch (moodle_exception $ex) {
857             $this->assertInstanceOf('invalid_parameter_exception', $ex);
858         }
860         $param = validate_param(null, PARAM_INT, true);
861         $this->assertSame(null, $param);
863         try {
864             $param = validate_param(array(), PARAM_INT);
865             $this->fail('invalid_parameter_exception expected');
866         } catch (moodle_exception $ex) {
867             $this->assertInstanceOf('invalid_parameter_exception', $ex);
868         }
869         try {
870             $param = validate_param(new stdClass, PARAM_INT);
871             $this->fail('invalid_parameter_exception expected');
872         } catch (moodle_exception $ex) {
873             $this->assertInstanceOf('invalid_parameter_exception', $ex);
874         }
876         $param = validate_param('1.0', PARAM_FLOAT);
877         $this->assertSame(1.0, $param);
879         // Make sure valid floats do not cause exception.
880         validate_param(1.0, PARAM_FLOAT);
881         validate_param(10, PARAM_FLOAT);
882         validate_param('0', PARAM_FLOAT);
883         validate_param('119813454.545464564564546564545646556564465465456465465465645645465645645645', PARAM_FLOAT);
884         validate_param('011.1', PARAM_FLOAT);
885         validate_param('11', PARAM_FLOAT);
886         validate_param('+.1', PARAM_FLOAT);
887         validate_param('-.1', PARAM_FLOAT);
888         validate_param('1e10', PARAM_FLOAT);
889         validate_param('.1e+10', PARAM_FLOAT);
890         validate_param('1E-1', PARAM_FLOAT);
892         try {
893             $param = validate_param('1,2', PARAM_FLOAT);
894             $this->fail('invalid_parameter_exception expected');
895         } catch (moodle_exception $ex) {
896             $this->assertInstanceOf('invalid_parameter_exception', $ex);
897         }
898         try {
899             $param = validate_param('', PARAM_FLOAT);
900             $this->fail('invalid_parameter_exception expected');
901         } catch (moodle_exception $ex) {
902             $this->assertInstanceOf('invalid_parameter_exception', $ex);
903         }
904         try {
905             $param = validate_param('.', PARAM_FLOAT);
906             $this->fail('invalid_parameter_exception expected');
907         } catch (moodle_exception $ex) {
908             $this->assertInstanceOf('invalid_parameter_exception', $ex);
909         }
910         try {
911             $param = validate_param('e10', PARAM_FLOAT);
912             $this->fail('invalid_parameter_exception expected');
913         } catch (moodle_exception $ex) {
914             $this->assertInstanceOf('invalid_parameter_exception', $ex);
915         }
916         try {
917             $param = validate_param('abc', PARAM_FLOAT);
918             $this->fail('invalid_parameter_exception expected');
919         } catch (moodle_exception $ex) {
920             $this->assertInstanceOf('invalid_parameter_exception', $ex);
921         }
922     }
924     public function test_shorten_text_no_tags_already_short_enough() {
925         // ......12345678901234567890123456.
926         $text = "short text already no tags";
927         $this->assertSame($text, shorten_text($text));
928     }
930     public function test_shorten_text_with_tags_already_short_enough() {
931         // .........123456...7890....12345678.......901234567.
932         $text = "<p>short <b>text</b> already</p><p>with tags</p>";
933         $this->assertSame($text, shorten_text($text));
934     }
936     public function test_shorten_text_no_tags_needs_shortening() {
937         // Default truncation is after 30 chars, but allowing 3 for the final '...'.
938         // ......12345678901234567890123456789023456789012345678901234.
939         $text = "long text without any tags blah de blah blah blah what";
940         $this->assertSame('long text without any tags ...', shorten_text($text));
941     }
943     public function test_shorten_text_with_tags_needs_shortening() {
944         // .......................................123456789012345678901234567890...
945         $text = "<div class='frog'><p><blockquote>Long text with tags that will ".
946             "be chopped off but <b>should be added back again</b></blockquote></p></div>";
947         $this->assertEquals("<div class='frog'><p><blockquote>Long text with " .
948             "tags that ...</blockquote></p></div>", shorten_text($text));
949     }
951     public function test_shorten_text_with_tags_and_html_comment() {
952         $text = "<div class='frog'><p><blockquote><!--[if !IE]><!-->Long text with ".
953             "tags that will<!--<![endif]--> ".
954             "be chopped off but <b>should be added back again</b></blockquote></p></div>";
955         $this->assertEquals("<div class='frog'><p><blockquote><!--[if !IE]><!-->Long text with " .
956             "tags that ...<!--<![endif]--></blockquote></p></div>", shorten_text($text));
957     }
959     public function test_shorten_text_with_entities() {
960         // Remember to allow 3 chars for the final '...'.
961         // ......123456789012345678901234567_____890...
962         $text = "some text which shouldn't &nbsp; break there";
963         $this->assertSame("some text which shouldn't &nbsp; ...", shorten_text($text, 31));
964         $this->assertSame("some text which shouldn't &nbsp;...", shorten_text($text, 30));
965         $this->assertSame("some text which shouldn't ...", shorten_text($text, 29));
966     }
968     public function test_shorten_text_known_tricky_case() {
969         // This case caused a bug up to 1.9.5
970         // ..........123456789012345678901234567890123456789.....0_____1___2___...
971         $text = "<h3>standard 'break-out' sub groups in TGs?</h3>&nbsp;&lt;&lt;There are several";
972         $this->assertSame("<h3>standard 'break-out' sub groups in ...</h3>",
973             shorten_text($text, 41));
974         $this->assertSame("<h3>standard 'break-out' sub groups in TGs?...</h3>",
975             shorten_text($text, 42));
976         $this->assertSame("<h3>standard 'break-out' sub groups in TGs?</h3>&nbsp;...",
977             shorten_text($text, 43));
978     }
980     public function test_shorten_text_no_spaces() {
981         // ..........123456789.
982         $text = "<h1>123456789</h1>"; // A string with no convenient breaks.
983         $this->assertSame("<h1>12345...</h1>", shorten_text($text, 8));
984     }
986     public function test_shorten_text_utf8_european() {
987         // Text without tags.
988         // ......123456789012345678901234567.
989         $text = "Žluťoučký koníček přeskočil";
990         $this->assertSame($text, shorten_text($text)); // 30 chars by default.
991         $this->assertSame("Žluťoučký koníče...", shorten_text($text, 19, true));
992         $this->assertSame("Žluťoučký ...", shorten_text($text, 19, false));
993         // And try it with 2-less (that are, in bytes, the middle of a sequence).
994         $this->assertSame("Žluťoučký koní...", shorten_text($text, 17, true));
995         $this->assertSame("Žluťoučký ...", shorten_text($text, 17, false));
997         // .........123456789012345678...901234567....89012345.
998         $text = "<p>Žluťoučký koníček <b>přeskočil</b> potůček</p>";
999         $this->assertSame($text, shorten_text($text, 60));
1000         $this->assertSame("<p>Žluťoučký koníček ...</p>", shorten_text($text, 21));
1001         $this->assertSame("<p>Žluťoučký koníče...</p>", shorten_text($text, 19, true));
1002         $this->assertSame("<p>Žluťoučký ...</p>", shorten_text($text, 19, false));
1003         // And try it with 2 fewer (that are, in bytes, the middle of a sequence).
1004         $this->assertSame("<p>Žluťoučký koní...</p>", shorten_text($text, 17, true));
1005         $this->assertSame("<p>Žluťoučký ...</p>", shorten_text($text, 17, false));
1006         // And try over one tag (start/end), it does proper text len.
1007         $this->assertSame("<p>Žluťoučký koníček <b>př...</b></p>", shorten_text($text, 23, true));
1008         $this->assertSame("<p>Žluťoučký koníček <b>přeskočil</b> pot...</p>", shorten_text($text, 34, true));
1009         // And in the middle of one tag.
1010         $this->assertSame("<p>Žluťoučký koníček <b>přeskočil...</b></p>", shorten_text($text, 30, true));
1011     }
1013     public function test_shorten_text_utf8_oriental() {
1014         // Japanese
1015         // text without tags
1016         // ......123456789012345678901234.
1017         $text = '言語設定言語設定abcdefghijkl';
1018         $this->assertSame($text, shorten_text($text)); // 30 chars by default.
1019         $this->assertSame("言語設定言語...", shorten_text($text, 9, true));
1020         $this->assertSame("言語設定言語...", shorten_text($text, 9, false));
1021         $this->assertSame("言語設定言語設定ab...", shorten_text($text, 13, true));
1022         $this->assertSame("言語設定言語設定...", shorten_text($text, 13, false));
1024         // Chinese
1025         // text without tags
1026         // ......123456789012345678901234.
1027         $text = '简体中文简体中文abcdefghijkl';
1028         $this->assertSame($text, shorten_text($text)); // 30 chars by default.
1029         $this->assertSame("简体中文简体...", shorten_text($text, 9, true));
1030         $this->assertSame("简体中文简体...", shorten_text($text, 9, false));
1031         $this->assertSame("简体中文简体中文ab...", shorten_text($text, 13, true));
1032         $this->assertSame("简体中文简体中文...", shorten_text($text, 13, false));
1033     }
1035     public function test_shorten_text_multilang() {
1036         // This is not necessaryily specific to multilang. The issue is really
1037         // tags with attributes, where before we were generating invalid HTML
1038         // output like shorten_text('<span id="x" class="y">A</span> B', 1)
1039         // returning '<span id="x" ...</span>'. It is just that multilang
1040         // requires the sort of HTML that is quite likely to trigger this.
1041         // ........................................1...
1042         $text = '<span lang="en" class="multilang">A</span>' .
1043                 '<span lang="fr" class="multilang">B</span>';
1044         $this->assertSame('<span lang="en" class="multilang">...</span>',
1045                 shorten_text($text, 1));
1046     }
1048     /**
1049      * Provider for long filenames and its expected result, with and without hash.
1050      *
1051      * @return array of ($filename, $length, $expected, $includehash)
1052      */
1053     public function shorten_filename_provider() {
1054         $filename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium totam rem';
1055         $shortfilename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque';
1057         return [
1058             'More than 100 characters' => [
1059                 $filename,
1060                 null,
1061                 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium tot',
1062                 false,
1063             ],
1064             'More than 100 characters with hash' => [
1065                 $filename,
1066                 null,
1067                 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque l - 3bec1da8b8',
1068                 true,
1069             ],
1070             'More than 100 characters with extension' => [
1071                 "{$filename}.zip",
1072                 null,
1073                 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium tot.zip',
1074                 false,
1075             ],
1076             'More than 100 characters with extension and hash' => [
1077                 "{$filename}.zip",
1078                 null,
1079                 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque l - 3bec1da8b8.zip',
1080                 true,
1081             ],
1082             'Limit filename to 50 chars' => [
1083                 $filename,
1084                 50,
1085                 'sed ut perspiciatis unde omnis iste natus error si',
1086                 false,
1087             ],
1088             'Limit filename to 50 chars with hash' => [
1089                 $filename,
1090                 50,
1091                 'sed ut perspiciatis unde omnis iste n - 3bec1da8b8',
1092                 true,
1093             ],
1094             'Limit filename to 50 chars with extension' => [
1095                 "{$filename}.zip",
1096                 50,
1097                 'sed ut perspiciatis unde omnis iste natus error si.zip',
1098                 false,
1099             ],
1100             'Limit filename to 50 chars with extension and hash' => [
1101                 "{$filename}.zip",
1102                 50,
1103                 'sed ut perspiciatis unde omnis iste n - 3bec1da8b8.zip',
1104                 true,
1105             ],
1106             'Test filename that contains less than 100 characters' => [
1107                 $shortfilename,
1108                 null,
1109                 $shortfilename,
1110                 false,
1111             ],
1112             'Test filename that contains less than 100 characters and hash' => [
1113                 $shortfilename,
1114                 null,
1115                 $shortfilename,
1116                 true,
1117             ],
1118             'Test filename that contains less than 100 characters with extension' => [
1119                 "{$shortfilename}.zip",
1120                 null,
1121                 "{$shortfilename}.zip",
1122                 false,
1123             ],
1124             'Test filename that contains less than 100 characters with extension and hash' => [
1125                 "{$shortfilename}.zip",
1126                 null,
1127                 "{$shortfilename}.zip",
1128                 true,
1129             ],
1130         ];
1131     }
1133     /**
1134      * Test the {@link shorten_filename()} method.
1135      *
1136      * @dataProvider shorten_filename_provider
1137      *
1138      * @param string $filename
1139      * @param int $length
1140      * @param string $expected
1141      * @param boolean $includehash
1142      */
1143     public function test_shorten_filename($filename, $length, $expected, $includehash) {
1144         if (null === $length) {
1145             $length = MAX_FILENAME_SIZE;
1146         }
1148         $this->assertSame($expected, shorten_filename($filename, $length, $includehash));
1149     }
1151     /**
1152      * Provider for long filenames and its expected result, with and without hash.
1153      *
1154      * @return array of ($filename, $length, $expected, $includehash)
1155      */
1156     public function shorten_filenames_provider() {
1157         $shortfilename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque';
1158         $longfilename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium totam rem';
1159         $extfilename = $longfilename.'.zip';
1160         $expected = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium tot';
1161         $expectedwithhash = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque l - 3bec1da8b8';
1162         $expectedext = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium tot.zip';
1163         $expectedextwithhash = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque l - 3bec1da8b8.zip';
1164         $expected50 = 'sed ut perspiciatis unde omnis iste natus error si';
1165         $expected50withhash = 'sed ut perspiciatis unde omnis iste n - 3bec1da8b8';
1166         $expected50ext = 'sed ut perspiciatis unde omnis iste natus error si.zip';
1167         $expected50extwithhash = 'sed ut perspiciatis unde omnis iste n - 3bec1da8b8.zip';
1168         $expected50short = 'sed ut perspiciatis unde omnis iste n - 5fb6543490';
1170         return [
1171             'Empty array without hash' => [
1172                 [],
1173                 null,
1174                 [],
1175                 false,
1176             ],
1177             'Empty array with hash' => [
1178                 [],
1179                 null,
1180                 [],
1181                 true,
1182             ],
1183             'Array with less than 100 characters' => [
1184                 [$shortfilename, $shortfilename, $shortfilename],
1185                 null,
1186                 [$shortfilename, $shortfilename, $shortfilename],
1187                 false,
1188             ],
1189             'Array with more than 100 characters without hash' => [
1190                 [$longfilename, $longfilename, $longfilename],
1191                 null,
1192                 [$expected, $expected, $expected],
1193                 false,
1194             ],
1195             'Array with more than 100 characters with hash' => [
1196                 [$longfilename, $longfilename, $longfilename],
1197                 null,
1198                 [$expectedwithhash, $expectedwithhash, $expectedwithhash],
1199                 true,
1200             ],
1201             'Array with more than 100 characters with extension' => [
1202                 [$extfilename, $extfilename, $extfilename],
1203                 null,
1204                 [$expectedext, $expectedext, $expectedext],
1205                 false,
1206             ],
1207             'Array with more than 100 characters with extension and hash' => [
1208                 [$extfilename, $extfilename, $extfilename],
1209                 null,
1210                 [$expectedextwithhash, $expectedextwithhash, $expectedextwithhash],
1211                 true,
1212             ],
1213             'Array with more than 100 characters mix (short, long, with extension) without hash' => [
1214                 [$shortfilename, $longfilename, $extfilename],
1215                 null,
1216                 [$shortfilename, $expected, $expectedext],
1217                 false,
1218             ],
1219             'Array with more than 100 characters mix (short, long, with extension) with hash' => [
1220                 [$shortfilename, $longfilename, $extfilename],
1221                 null,
1222                 [$shortfilename, $expectedwithhash, $expectedextwithhash],
1223                 true,
1224             ],
1225             'Array with less than 50 characters without hash' => [
1226                 [$longfilename, $longfilename, $longfilename],
1227                 50,
1228                 [$expected50, $expected50, $expected50],
1229                 false,
1230             ],
1231             'Array with less than 50 characters with hash' => [
1232                 [$longfilename, $longfilename, $longfilename],
1233                 50,
1234                 [$expected50withhash, $expected50withhash, $expected50withhash],
1235                 true,
1236             ],
1237             'Array with less than 50 characters with extension' => [
1238                 [$extfilename, $extfilename, $extfilename],
1239                 50,
1240                 [$expected50ext, $expected50ext, $expected50ext],
1241                 false,
1242             ],
1243             'Array with less than 50 characters with extension and hash' => [
1244                 [$extfilename, $extfilename, $extfilename],
1245                 50,
1246                 [$expected50extwithhash, $expected50extwithhash, $expected50extwithhash],
1247                 true,
1248             ],
1249             'Array with less than 50 characters mix (short, long, with extension) without hash' => [
1250                 [$shortfilename, $longfilename, $extfilename],
1251                 50,
1252                 [$expected50, $expected50, $expected50ext],
1253                 false,
1254             ],
1255             'Array with less than 50 characters mix (short, long, with extension) with hash' => [
1256                 [$shortfilename, $longfilename, $extfilename],
1257                 50,
1258                 [$expected50short, $expected50withhash, $expected50extwithhash],
1259                 true,
1260             ],
1261         ];
1262     }
1264     /**
1265      * Test the {@link shorten_filenames()} method.
1266      *
1267      * @dataProvider shorten_filenames_provider
1268      *
1269      * @param string $filenames
1270      * @param int $length
1271      * @param string $expected
1272      * @param boolean $includehash
1273      */
1274     public function test_shorten_filenames($filenames, $length, $expected, $includehash) {
1275         if (null === $length) {
1276             $length = MAX_FILENAME_SIZE;
1277         }
1279         $this->assertSame($expected, shorten_filenames($filenames, $length, $includehash));
1280     }
1282     public function test_usergetdate() {
1283         global $USER, $CFG, $DB;
1284         $this->resetAfterTest();
1286         $this->setAdminUser();
1288         $USER->timezone = 2;// Set the timezone to a known state.
1290         $ts = 1261540267; // The time this function was created.
1292         $arr = usergetdate($ts, 1); // Specify the timezone as an argument.
1293         $arr = array_values($arr);
1295         list($seconds, $minutes, $hours, $mday, $wday, $mon, $year, $yday, $weekday, $month) = $arr;
1296         $this->assertSame(7, $seconds);
1297         $this->assertSame(51, $minutes);
1298         $this->assertSame(4, $hours);
1299         $this->assertSame(23, $mday);
1300         $this->assertSame(3, $wday);
1301         $this->assertSame(12, $mon);
1302         $this->assertSame(2009, $year);
1303         $this->assertSame(356, $yday);
1304         $this->assertSame('Wednesday', $weekday);
1305         $this->assertSame('December', $month);
1306         $arr = usergetdate($ts); // Gets the timezone from the $USER object.
1307         $arr = array_values($arr);
1309         list($seconds, $minutes, $hours, $mday, $wday, $mon, $year, $yday, $weekday, $month) = $arr;
1310         $this->assertSame(7, $seconds);
1311         $this->assertSame(51, $minutes);
1312         $this->assertSame(5, $hours);
1313         $this->assertSame(23, $mday);
1314         $this->assertSame(3, $wday);
1315         $this->assertSame(12, $mon);
1316         $this->assertSame(2009, $year);
1317         $this->assertSame(356, $yday);
1318         $this->assertSame('Wednesday', $weekday);
1319         $this->assertSame('December', $month);
1320     }
1322     public function test_mark_user_preferences_changed() {
1323         $this->resetAfterTest();
1324         $otheruser = $this->getDataGenerator()->create_user();
1325         $otheruserid = $otheruser->id;
1327         set_cache_flag('userpreferenceschanged', $otheruserid, null);
1328         mark_user_preferences_changed($otheruserid);
1330         $this->assertEquals(get_cache_flag('userpreferenceschanged', $otheruserid, time()-10), 1);
1331         set_cache_flag('userpreferenceschanged', $otheruserid, null);
1332     }
1334     public function test_check_user_preferences_loaded() {
1335         global $DB;
1336         $this->resetAfterTest();
1338         $otheruser = $this->getDataGenerator()->create_user();
1339         $otheruserid = $otheruser->id;
1341         $DB->delete_records('user_preferences', array('userid'=>$otheruserid));
1342         set_cache_flag('userpreferenceschanged', $otheruserid, null);
1344         $user = new stdClass();
1345         $user->id = $otheruserid;
1347         // Load.
1348         check_user_preferences_loaded($user);
1349         $this->assertTrue(isset($user->preference));
1350         $this->assertTrue(is_array($user->preference));
1351         $this->assertArrayHasKey('_lastloaded', $user->preference);
1352         $this->assertCount(1, $user->preference);
1354         // Add preference via direct call.
1355         $DB->insert_record('user_preferences', array('name'=>'xxx', 'value'=>'yyy', 'userid'=>$user->id));
1357         // No cache reload yet.
1358         check_user_preferences_loaded($user);
1359         $this->assertCount(1, $user->preference);
1361         // Forced reloading of cache.
1362         unset($user->preference);
1363         check_user_preferences_loaded($user);
1364         $this->assertCount(2, $user->preference);
1365         $this->assertSame('yyy', $user->preference['xxx']);
1367         // Add preference via direct call.
1368         $DB->insert_record('user_preferences', array('name'=>'aaa', 'value'=>'bbb', 'userid'=>$user->id));
1370         // Test timeouts and modifications from different session.
1371         set_cache_flag('userpreferenceschanged', $user->id, 1, time() + 1000);
1372         $user->preference['_lastloaded'] = $user->preference['_lastloaded'] - 20;
1373         check_user_preferences_loaded($user);
1374         $this->assertCount(2, $user->preference);
1375         check_user_preferences_loaded($user, 10);
1376         $this->assertCount(3, $user->preference);
1377         $this->assertSame('bbb', $user->preference['aaa']);
1378         set_cache_flag('userpreferenceschanged', $user->id, null);
1379     }
1381     public function test_set_user_preference() {
1382         global $DB, $USER;
1383         $this->resetAfterTest();
1385         $this->setAdminUser();
1387         $otheruser = $this->getDataGenerator()->create_user();
1388         $otheruserid = $otheruser->id;
1390         $DB->delete_records('user_preferences', array('userid'=>$otheruserid));
1391         set_cache_flag('userpreferenceschanged', $otheruserid, null);
1393         $user = new stdClass();
1394         $user->id = $otheruserid;
1396         set_user_preference('aaa', 'bbb', $otheruserid);
1397         $this->assertSame('bbb', $DB->get_field('user_preferences', 'value', array('userid'=>$otheruserid, 'name'=>'aaa')));
1398         $this->assertSame('bbb', get_user_preferences('aaa', null, $otheruserid));
1400         set_user_preference('xxx', 'yyy', $user);
1401         $this->assertSame('yyy', $DB->get_field('user_preferences', 'value', array('userid'=>$otheruserid, 'name'=>'xxx')));
1402         $this->assertSame('yyy', get_user_preferences('xxx', null, $otheruserid));
1403         $this->assertTrue(is_array($user->preference));
1404         $this->assertSame('bbb', $user->preference['aaa']);
1405         $this->assertSame('yyy', $user->preference['xxx']);
1407         set_user_preference('xxx', null, $user);
1408         $this->assertFalse($DB->get_field('user_preferences', 'value', array('userid'=>$otheruserid, 'name'=>'xxx')));
1409         $this->assertNull(get_user_preferences('xxx', null, $otheruserid));
1411         set_user_preference('ooo', true, $user);
1412         $prefs = get_user_preferences(null, null, $otheruserid);
1413         $this->assertSame($user->preference['aaa'], $prefs['aaa']);
1414         $this->assertSame($user->preference['ooo'], $prefs['ooo']);
1415         $this->assertSame('1', $prefs['ooo']);
1417         set_user_preference('null', 0, $user);
1418         $this->assertSame('0', get_user_preferences('null', null, $otheruserid));
1420         $this->assertSame('lala', get_user_preferences('undefined', 'lala', $otheruserid));
1422         $DB->delete_records('user_preferences', array('userid'=>$otheruserid));
1423         set_cache_flag('userpreferenceschanged', $otheruserid, null);
1425         // Test $USER default.
1426         set_user_preference('_test_user_preferences_pref', 'ok');
1427         $this->assertSame('ok', $USER->preference['_test_user_preferences_pref']);
1428         unset_user_preference('_test_user_preferences_pref');
1429         $this->assertTrue(!isset($USER->preference['_test_user_preferences_pref']));
1431         // Test 1333 char values (no need for unicode, there are already tests for that in DB tests).
1432         $longvalue = str_repeat('a', 1333);
1433         set_user_preference('_test_long_user_preference', $longvalue);
1434         $this->assertEquals($longvalue, get_user_preferences('_test_long_user_preference'));
1435         $this->assertEquals($longvalue,
1436             $DB->get_field('user_preferences', 'value', array('userid' => $USER->id, 'name' => '_test_long_user_preference')));
1438         // Test > 1333 char values, coding_exception expected.
1439         $longvalue = str_repeat('a', 1334);
1440         try {
1441             set_user_preference('_test_long_user_preference', $longvalue);
1442             $this->fail('Exception expected - longer than 1333 chars not allowed as preference value');
1443         } catch (moodle_exception $ex) {
1444             $this->assertInstanceOf('coding_exception', $ex);
1445         }
1447         // Test invalid params.
1448         try {
1449             set_user_preference('_test_user_preferences_pref', array());
1450             $this->fail('Exception expected - array not valid preference value');
1451         } catch (moodle_exception $ex) {
1452             $this->assertInstanceOf('coding_exception', $ex);
1453         }
1454         try {
1455             set_user_preference('_test_user_preferences_pref', new stdClass);
1456             $this->fail('Exception expected - class not valid preference value');
1457         } catch (moodle_exception $ex) {
1458             $this->assertInstanceOf('coding_exception', $ex);
1459         }
1460         try {
1461             set_user_preference('_test_user_preferences_pref', 1, array('xx' => 1));
1462             $this->fail('Exception expected - user instance expected');
1463         } catch (moodle_exception $ex) {
1464             $this->assertInstanceOf('coding_exception', $ex);
1465         }
1466         try {
1467             set_user_preference('_test_user_preferences_pref', 1, 'abc');
1468             $this->fail('Exception expected - user instance expected');
1469         } catch (moodle_exception $ex) {
1470             $this->assertInstanceOf('coding_exception', $ex);
1471         }
1472         try {
1473             set_user_preference('', 1);
1474             $this->fail('Exception expected - invalid name accepted');
1475         } catch (moodle_exception $ex) {
1476             $this->assertInstanceOf('coding_exception', $ex);
1477         }
1478         try {
1479             set_user_preference('1', 1);
1480             $this->fail('Exception expected - invalid name accepted');
1481         } catch (moodle_exception $ex) {
1482             $this->assertInstanceOf('coding_exception', $ex);
1483         }
1484     }
1486     public function test_set_user_preference_for_current_user() {
1487         global $USER;
1488         $this->resetAfterTest();
1489         $this->setAdminUser();
1491         set_user_preference('test_pref', 2);
1492         set_user_preference('test_pref', 1, $USER->id);
1493         $this->assertEquals(1, get_user_preferences('test_pref'));
1494     }
1496     public function test_unset_user_preference_for_current_user() {
1497         global $USER;
1498         $this->resetAfterTest();
1499         $this->setAdminUser();
1501         set_user_preference('test_pref', 1);
1502         unset_user_preference('test_pref', $USER->id);
1503         $this->assertNull(get_user_preferences('test_pref'));
1504     }
1506     /**
1507      * Test essential features implementation of {@link get_extra_user_fields()} as the admin user with all capabilities.
1508      */
1509     public function test_get_extra_user_fields_essentials() {
1510         global $CFG, $USER, $DB;
1511         $this->resetAfterTest();
1513         $this->setAdminUser();
1514         $context = context_system::instance();
1516         // No fields.
1517         $CFG->showuseridentity = '';
1518         $this->assertEquals(array(), get_extra_user_fields($context));
1520         // One field.
1521         $CFG->showuseridentity = 'frog';
1522         $this->assertEquals(array('frog'), get_extra_user_fields($context));
1524         // Two fields.
1525         $CFG->showuseridentity = 'frog,zombie';
1526         $this->assertEquals(array('frog', 'zombie'), get_extra_user_fields($context));
1528         // No fields, except.
1529         $CFG->showuseridentity = '';
1530         $this->assertEquals(array(), get_extra_user_fields($context, array('frog')));
1532         // One field.
1533         $CFG->showuseridentity = 'frog';
1534         $this->assertEquals(array(), get_extra_user_fields($context, array('frog')));
1536         // Two fields.
1537         $CFG->showuseridentity = 'frog,zombie';
1538         $this->assertEquals(array('zombie'), get_extra_user_fields($context, array('frog')));
1539     }
1541     /**
1542      * Prepare environment for couple of tests related to permission checks in {@link get_extra_user_fields()}.
1543      *
1544      * @return stdClass
1545      */
1546     protected function environment_for_get_extra_user_fields_tests() {
1547         global $CFG, $DB;
1549         $CFG->showuseridentity = 'idnumber,country,city';
1550         $CFG->hiddenuserfields = 'country,city';
1552         $env = new stdClass();
1554         $env->course = $this->getDataGenerator()->create_course();
1555         $env->coursecontext = context_course::instance($env->course->id);
1557         $env->teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1558         $env->studentrole = $DB->get_record('role', array('shortname' => 'student'));
1559         $env->managerrole = $DB->get_record('role', array('shortname' => 'manager'));
1561         $env->student = $this->getDataGenerator()->create_user();
1562         $env->teacher = $this->getDataGenerator()->create_user();
1563         $env->manager = $this->getDataGenerator()->create_user();
1565         role_assign($env->studentrole->id, $env->student->id, $env->coursecontext->id);
1566         role_assign($env->teacherrole->id, $env->teacher->id, $env->coursecontext->id);
1567         role_assign($env->managerrole->id, $env->manager->id, SYSCONTEXTID);
1569         return $env;
1570     }
1572     /**
1573      * No identity fields shown to student user (no permission to view identity fields).
1574      */
1575     public function test_get_extra_user_fields_no_access() {
1577         $this->resetAfterTest();
1578         $env = $this->environment_for_get_extra_user_fields_tests();
1579         $this->setUser($env->student);
1581         $this->assertEquals(array(), get_extra_user_fields($env->coursecontext));
1582         $this->assertEquals(array(), get_extra_user_fields(context_system::instance()));
1583     }
1585     /**
1586      * Teacher can see students' identity fields only within the course.
1587      */
1588     public function test_get_extra_user_fields_course_only_access() {
1590         $this->resetAfterTest();
1591         $env = $this->environment_for_get_extra_user_fields_tests();
1592         $this->setUser($env->teacher);
1594         $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext));
1595         $this->assertEquals(array(), get_extra_user_fields(context_system::instance()));
1596     }
1598     /**
1599      * Teacher can be prevented from seeing students' identity fields even within the course.
1600      */
1601     public function test_get_extra_user_fields_course_prevented_access() {
1603         $this->resetAfterTest();
1604         $env = $this->environment_for_get_extra_user_fields_tests();
1605         $this->setUser($env->teacher);
1607         assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->teacherrole->id, $env->coursecontext->id);
1608         $this->assertEquals(array('idnumber'), get_extra_user_fields($env->coursecontext));
1609     }
1611     /**
1612      * Manager can see students' identity fields anywhere.
1613      */
1614     public function test_get_extra_user_fields_anywhere_access() {
1616         $this->resetAfterTest();
1617         $env = $this->environment_for_get_extra_user_fields_tests();
1618         $this->setUser($env->manager);
1620         $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext));
1621         $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields(context_system::instance()));
1622     }
1624     /**
1625      * Manager can be prevented from seeing hidden fields outside the course.
1626      */
1627     public function test_get_extra_user_fields_schismatic_access() {
1629         $this->resetAfterTest();
1630         $env = $this->environment_for_get_extra_user_fields_tests();
1631         $this->setUser($env->manager);
1633         assign_capability('moodle/user:viewhiddendetails', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
1634         $this->assertEquals(array('idnumber'), get_extra_user_fields(context_system::instance()));
1635         // Note that inside the course, the manager can still see the hidden identifiers as this is currently
1636         // controlled by a separate capability for legacy reasons.
1637         $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext));
1638     }
1640     /**
1641      * Two capabilities must be currently set to prevent manager from seeing hidden fields.
1642      */
1643     public function test_get_extra_user_fields_hard_to_prevent_access() {
1645         $this->resetAfterTest();
1646         $env = $this->environment_for_get_extra_user_fields_tests();
1647         $this->setUser($env->manager);
1649         assign_capability('moodle/user:viewhiddendetails', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
1650         assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
1652         $this->assertEquals(array('idnumber'), get_extra_user_fields(context_system::instance()));
1653         $this->assertEquals(array('idnumber'), get_extra_user_fields($env->coursecontext));
1654     }
1656     public function test_get_extra_user_fields_sql() {
1657         global $CFG, $USER, $DB;
1658         $this->resetAfterTest();
1660         $this->setAdminUser();
1662         $context = context_system::instance();
1664         // No fields.
1665         $CFG->showuseridentity = '';
1666         $this->assertSame('', get_extra_user_fields_sql($context));
1668         // One field.
1669         $CFG->showuseridentity = 'frog';
1670         $this->assertSame(', frog', get_extra_user_fields_sql($context));
1672         // Two fields with table prefix.
1673         $CFG->showuseridentity = 'frog,zombie';
1674         $this->assertSame(', u1.frog, u1.zombie', get_extra_user_fields_sql($context, 'u1'));
1676         // Two fields with field prefix.
1677         $CFG->showuseridentity = 'frog,zombie';
1678         $this->assertSame(', frog AS u_frog, zombie AS u_zombie',
1679             get_extra_user_fields_sql($context, '', 'u_'));
1681         // One field excluded.
1682         $CFG->showuseridentity = 'frog';
1683         $this->assertSame('', get_extra_user_fields_sql($context, '', '', array('frog')));
1685         // Two fields, one excluded, table+field prefix.
1686         $CFG->showuseridentity = 'frog,zombie';
1687         $this->assertEquals(', u1.zombie AS u_zombie',
1688             get_extra_user_fields_sql($context, 'u1', 'u_', array('frog')));
1689     }
1691     /**
1692      * Test some critical TZ/DST.
1693      *
1694      * This method tests some special TZ/DST combinations that were fixed
1695      * by MDL-38999. The tests are done by comparing the results of the
1696      * output using Moodle TZ/DST support and PHP native one.
1697      *
1698      * Note: If you don't trust PHP TZ/DST support, can verify the
1699      * harcoded expectations below with:
1700      * http://www.tools4noobs.com/online_tools/unix_timestamp_to_datetime/
1701      */
1702     public function test_some_moodle_special_dst() {
1703         $stamp = 1365386400; // 2013/04/08 02:00:00 GMT/UTC.
1705         // In Europe/Tallinn it was 2013/04/08 05:00:00.
1706         $expectation = '2013/04/08 05:00:00';
1707         $phpdt = DateTime::createFromFormat('U', $stamp, new DateTimeZone('UTC'));
1708         $phpdt->setTimezone(new DateTimeZone('Europe/Tallinn'));
1709         $phpres = $phpdt->format('Y/m/d H:i:s'); // PHP result.
1710         $moodleres = userdate($stamp, '%Y/%m/%d %H:%M:%S', 'Europe/Tallinn', false); // Moodle result.
1711         $this->assertSame($expectation, $phpres);
1712         $this->assertSame($expectation, $moodleres);
1714         // In St. Johns it was 2013/04/07 23:30:00.
1715         $expectation = '2013/04/07 23:30:00';
1716         $phpdt = DateTime::createFromFormat('U', $stamp, new DateTimeZone('UTC'));
1717         $phpdt->setTimezone(new DateTimeZone('America/St_Johns'));
1718         $phpres = $phpdt->format('Y/m/d H:i:s'); // PHP result.
1719         $moodleres = userdate($stamp, '%Y/%m/%d %H:%M:%S', 'America/St_Johns', false); // Moodle result.
1720         $this->assertSame($expectation, $phpres);
1721         $this->assertSame($expectation, $moodleres);
1723         $stamp = 1383876000; // 2013/11/08 02:00:00 GMT/UTC.
1725         // In Europe/Tallinn it was 2013/11/08 04:00:00.
1726         $expectation = '2013/11/08 04:00:00';
1727         $phpdt = DateTime::createFromFormat('U', $stamp, new DateTimeZone('UTC'));
1728         $phpdt->setTimezone(new DateTimeZone('Europe/Tallinn'));
1729         $phpres = $phpdt->format('Y/m/d H:i:s'); // PHP result.
1730         $moodleres = userdate($stamp, '%Y/%m/%d %H:%M:%S', 'Europe/Tallinn', false); // Moodle result.
1731         $this->assertSame($expectation, $phpres);
1732         $this->assertSame($expectation, $moodleres);
1734         // In St. Johns it was 2013/11/07 22:30:00.
1735         $expectation = '2013/11/07 22:30:00';
1736         $phpdt = DateTime::createFromFormat('U', $stamp, new DateTimeZone('UTC'));
1737         $phpdt->setTimezone(new DateTimeZone('America/St_Johns'));
1738         $phpres = $phpdt->format('Y/m/d H:i:s'); // PHP result.
1739         $moodleres = userdate($stamp, '%Y/%m/%d %H:%M:%S', 'America/St_Johns', false); // Moodle result.
1740         $this->assertSame($expectation, $phpres);
1741         $this->assertSame($expectation, $moodleres);
1742     }
1744     public function test_userdate() {
1745         global $USER, $CFG, $DB;
1746         $this->resetAfterTest();
1748         $this->setAdminUser();
1750         $testvalues = array(
1751             array(
1752                 'time' => '1309514400',
1753                 'usertimezone' => 'America/Moncton',
1754                 'timezone' => '0.0', // No dst offset.
1755                 'expectedoutput' => 'Friday, 1 July 2011, 10:00 AM',
1756                 'expectedoutputhtml' => '<time datetime="2011-07-01T07:00:00-03:00">Friday, 1 July 2011, 10:00 AM</time>'
1757             ),
1758             array(
1759                 'time' => '1309514400',
1760                 'usertimezone' => 'America/Moncton',
1761                 'timezone' => '99', // Dst offset and timezone offset.
1762                 'expectedoutput' => 'Friday, 1 July 2011, 7:00 AM',
1763                 'expectedoutputhtml' => '<time datetime="2011-07-01T07:00:00-03:00">Friday, 1 July 2011, 7:00 AM</time>'
1764             ),
1765             array(
1766                 'time' => '1309514400',
1767                 'usertimezone' => 'America/Moncton',
1768                 'timezone' => 'America/Moncton', // Dst offset and timezone offset.
1769                 'expectedoutput' => 'Friday, 1 July 2011, 7:00 AM',
1770                 'expectedoutputhtml' => '<time datetime="2011-07-01t07:00:00-03:00">Friday, 1 July 2011, 7:00 AM</time>'
1771             ),
1772             array(
1773                 'time' => '1293876000 ',
1774                 'usertimezone' => 'America/Moncton',
1775                 'timezone' => '0.0', // No dst offset.
1776                 'expectedoutput' => 'Saturday, 1 January 2011, 10:00 AM',
1777                 'expectedoutputhtml' => '<time datetime="2011-01-01T06:00:00-04:00">Saturday, 1 January 2011, 10:00 AM</time>'
1778             ),
1779             array(
1780                 'time' => '1293876000 ',
1781                 'usertimezone' => 'America/Moncton',
1782                 'timezone' => '99', // No dst offset in jan, so just timezone offset.
1783                 'expectedoutput' => 'Saturday, 1 January 2011, 6:00 AM',
1784                 'expectedoutputhtml' => '<time datetime="2011-01-01T06:00:00-04:00">Saturday, 1 January 2011, 6:00 AM</time>'
1785             ),
1786             array(
1787                 'time' => '1293876000 ',
1788                 'usertimezone' => 'America/Moncton',
1789                 'timezone' => 'America/Moncton', // No dst offset in jan.
1790                 'expectedoutput' => 'Saturday, 1 January 2011, 6:00 AM',
1791                 'expectedoutputhtml' => '<time datetime="2011-01-01T06:00:00-04:00">Saturday, 1 January 2011, 6:00 AM</time>'
1792             ),
1793             array(
1794                 'time' => '1293876000 ',
1795                 'usertimezone' => '2',
1796                 'timezone' => '99', // Take user timezone.
1797                 'expectedoutput' => 'Saturday, 1 January 2011, 12:00 PM',
1798                 'expectedoutputhtml' => '<time datetime="2011-01-01T12:00:00+02:00">Saturday, 1 January 2011, 12:00 PM</time>'
1799             ),
1800             array(
1801                 'time' => '1293876000 ',
1802                 'usertimezone' => '-2',
1803                 'timezone' => '99', // Take user timezone.
1804                 'expectedoutput' => 'Saturday, 1 January 2011, 8:00 AM',
1805                 'expectedoutputhtml' => '<time datetime="2011-01-01T08:00:00-02:00">Saturday, 1 January 2011, 8:00 AM</time>'
1806             ),
1807             array(
1808                 'time' => '1293876000 ',
1809                 'usertimezone' => '-10',
1810                 'timezone' => '2', // Take this timezone.
1811                 'expectedoutput' => 'Saturday, 1 January 2011, 12:00 PM',
1812                 'expectedoutputhtml' => '<time datetime="2011-01-01T00:00:00-10:00">Saturday, 1 January 2011, 12:00 PM</time>'
1813             ),
1814             array(
1815                 'time' => '1293876000 ',
1816                 'usertimezone' => '-10',
1817                 'timezone' => '-2', // Take this timezone.
1818                 'expectedoutput' => 'Saturday, 1 January 2011, 8:00 AM',
1819                 'expectedoutputhtml' => '<time datetime="2011-01-01T00:00:00-10:00">Saturday, 1 January 2011, 8:00 AM</time>'
1820             ),
1821             array(
1822                 'time' => '1293876000 ',
1823                 'usertimezone' => '-10',
1824                 'timezone' => 'random/time', // This should show server time.
1825                 'expectedoutput' => 'Saturday, 1 January 2011, 6:00 PM',
1826                 'expectedoutputhtml' => '<time datetime="2011-01-01T00:00:00-10:00">Saturday, 1 January 2011, 6:00 PM</time>'
1827             ),
1828             array(
1829                 'time' => '1293876000 ',
1830                 'usertimezone' => '20', // Fallback to server time zone.
1831                 'timezone' => '99',     // This should show user time.
1832                 'expectedoutput' => 'Saturday, 1 January 2011, 6:00 PM',
1833                 'expectedoutputhtml' => '<time datetime="2011-01-01T18:00:00+08:00">Saturday, 1 January 2011, 6:00 PM</time>'
1834             ),
1835         );
1837         // Set default timezone to Australia/Perth, else time calculated
1838         // will not match expected values.
1839         $this->setTimezone(99, 'Australia/Perth');
1841         foreach ($testvalues as $vals) {
1842             $USER->timezone = $vals['usertimezone'];
1843             $actualoutput = userdate($vals['time'], '%A, %d %B %Y, %I:%M %p', $vals['timezone']);
1844             $actualoutputhtml = userdate_htmltime($vals['time'], '%A, %d %B %Y, %I:%M %p', $vals['timezone']);
1846             // On different systems case of AM PM changes so compare case insensitive.
1847             $vals['expectedoutput'] = core_text::strtolower($vals['expectedoutput']);
1848             $vals['expectedoutputhtml'] = core_text::strtolower($vals['expectedoutputhtml']);
1849             $actualoutput = core_text::strtolower($actualoutput);
1850             $actualoutputhtml = core_text::strtolower($actualoutputhtml);
1852             $this->assertSame($vals['expectedoutput'], $actualoutput,
1853                 "Expected: {$vals['expectedoutput']} => Actual: {$actualoutput} \ndata: " . var_export($vals, true));
1854             $this->assertSame($vals['expectedoutputhtml'], $actualoutputhtml,
1855                 "Expected: {$vals['expectedoutputhtml']} => Actual: {$actualoutputhtml} \ndata: " . var_export($vals, true));
1856         }
1857     }
1859     /**
1860      * Make sure the DST changes happen at the right time in Moodle.
1861      */
1862     public function test_dst_changes() {
1863         // DST switching in Prague.
1864         // From 2AM to 3AM in 1989.
1865         $date = new DateTime('1989-03-26T01:59:00+01:00');
1866         $this->assertSame('Sunday, 26 March 1989, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1867         $date = new DateTime('1989-03-26T02:01:00+01:00');
1868         $this->assertSame('Sunday, 26 March 1989, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1869         // From 3AM to 2AM in 1989 - not the same as the west Europe.
1870         $date = new DateTime('1989-09-24T01:59:00+01:00');
1871         $this->assertSame('Sunday, 24 September 1989, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1872         $date = new DateTime('1989-09-24T02:01:00+01:00');
1873         $this->assertSame('Sunday, 24 September 1989, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1874         // From 2AM to 3AM in 2014.
1875         $date = new DateTime('2014-03-30T01:59:00+01:00');
1876         $this->assertSame('Sunday, 30 March 2014, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1877         $date = new DateTime('2014-03-30T02:01:00+01:00');
1878         $this->assertSame('Sunday, 30 March 2014, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1879         // From 3AM to 2AM in 2014.
1880         $date = new DateTime('2014-10-26T01:59:00+01:00');
1881         $this->assertSame('Sunday, 26 October 2014, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1882         $date = new DateTime('2014-10-26T02:01:00+01:00');
1883         $this->assertSame('Sunday, 26 October 2014, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1884         // From 2AM to 3AM in 2020.
1885         $date = new DateTime('2020-03-29T01:59:00+01:00');
1886         $this->assertSame('Sunday, 29 March 2020, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1887         $date = new DateTime('2020-03-29T02:01:00+01:00');
1888         $this->assertSame('Sunday, 29 March 2020, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1889         // From 3AM to 2AM in 2020.
1890         $date = new DateTime('2020-10-25T01:59:00+01:00');
1891         $this->assertSame('Sunday, 25 October 2020, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1892         $date = new DateTime('2020-10-25T02:01:00+01:00');
1893         $this->assertSame('Sunday, 25 October 2020, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Europe/Prague'));
1895         // DST switching in NZ.
1896         // From 3AM to 2AM in 2015.
1897         $date = new DateTime('2015-04-05T02:59:00+13:00');
1898         $this->assertSame('Sunday, 5 April 2015, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Pacific/Auckland'));
1899         $date = new DateTime('2015-04-05T03:01:00+13:00');
1900         $this->assertSame('Sunday, 5 April 2015, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Pacific/Auckland'));
1901         // From 2AM to 3AM in 2009.
1902         $date = new DateTime('2015-09-27T01:59:00+12:00');
1903         $this->assertSame('Sunday, 27 September 2015, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Pacific/Auckland'));
1904         $date = new DateTime('2015-09-27T02:01:00+12:00');
1905         $this->assertSame('Sunday, 27 September 2015, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Pacific/Auckland'));
1907         // DST switching in Perth.
1908         // From 3AM to 2AM in 2009.
1909         $date = new DateTime('2008-03-30T01:59:00+08:00');
1910         $this->assertSame('Sunday, 30 March 2008, 02:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Australia/Perth'));
1911         $date = new DateTime('2008-03-30T02:01:00+08:00');
1912         $this->assertSame('Sunday, 30 March 2008, 02:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Australia/Perth'));
1913         // From 2AM to 3AM in 2009.
1914         $date = new DateTime('2008-10-26T01:59:00+08:00');
1915         $this->assertSame('Sunday, 26 October 2008, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Australia/Perth'));
1916         $date = new DateTime('2008-10-26T02:01:00+08:00');
1917         $this->assertSame('Sunday, 26 October 2008, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'Australia/Perth'));
1919         // DST switching in US.
1920         // From 2AM to 3AM in 2014.
1921         $date = new DateTime('2014-03-09T01:59:00-05:00');
1922         $this->assertSame('Sunday, 9 March 2014, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'America/New_York'));
1923         $date = new DateTime('2014-03-09T02:01:00-05:00');
1924         $this->assertSame('Sunday, 9 March 2014, 03:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'America/New_York'));
1925         // From 3AM to 2AM in 2014.
1926         $date = new DateTime('2014-11-02T01:59:00-04:00');
1927         $this->assertSame('Sunday, 2 November 2014, 01:59', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'America/New_York'));
1928         $date = new DateTime('2014-11-02T02:01:00-04:00');
1929         $this->assertSame('Sunday, 2 November 2014, 01:01', userdate($date->getTimestamp(), '%A, %d %B %Y, %H:%M', 'America/New_York'));
1930     }
1932     public function test_make_timestamp() {
1933         global $USER, $CFG, $DB;
1934         $this->resetAfterTest();
1936         $this->setAdminUser();
1938         $testvalues = array(
1939             array(
1940                 'usertimezone' => 'America/Moncton',
1941                 'year' => '2011',
1942                 'month' => '7',
1943                 'day' => '1',
1944                 'hour' => '10',
1945                 'minutes' => '00',
1946                 'seconds' => '00',
1947                 'timezone' => '0.0',
1948                 'applydst' => false, // No dst offset.
1949                 'expectedoutput' => '1309514400' // 6pm at UTC+0.
1950             ),
1951             array(
1952                 'usertimezone' => 'America/Moncton',
1953                 'year' => '2011',
1954                 'month' => '7',
1955                 'day' => '1',
1956                 'hour' => '10',
1957                 'minutes' => '00',
1958                 'seconds' => '00',
1959                 'timezone' => '99',  // User default timezone.
1960                 'applydst' => false, // Don't apply dst.
1961                 'expectedoutput' => '1309528800'
1962             ),
1963             array(
1964                 'usertimezone' => 'America/Moncton',
1965                 'year' => '2011',
1966                 'month' => '7',
1967                 'day' => '1',
1968                 'hour' => '10',
1969                 'minutes' => '00',
1970                 'seconds' => '00',
1971                 'timezone' => '99', // User default timezone.
1972                 'applydst' => true, // Apply dst.
1973                 'expectedoutput' => '1309525200'
1974             ),
1975             array(
1976                 'usertimezone' => 'America/Moncton',
1977                 'year' => '2011',
1978                 'month' => '7',
1979                 'day' => '1',
1980                 'hour' => '10',
1981                 'minutes' => '00',
1982                 'seconds' => '00',
1983                 'timezone' => 'America/Moncton', // String timezone.
1984                 'applydst' => true, // Apply dst.
1985                 'expectedoutput' => '1309525200'
1986             ),
1987             array(
1988                 'usertimezone' => '2', // No dst applyed.
1989                 'year' => '2011',
1990                 'month' => '7',
1991                 'day' => '1',
1992                 'hour' => '10',
1993                 'minutes' => '00',
1994                 'seconds' => '00',
1995                 'timezone' => '99', // Take user timezone.
1996                 'applydst' => true, // Apply dst.
1997                 'expectedoutput' => '1309507200'
1998             ),
1999             array(
2000                 'usertimezone' => '-2', // No dst applyed.
2001                 'year' => '2011',
2002                 'month' => '7',
2003                 'day' => '1',
2004                 'hour' => '10',
2005                 'minutes' => '00',
2006                 'seconds' => '00',
2007                 'timezone' => '99', // Take usertimezone.
2008                 'applydst' => true, // Apply dst.
2009                 'expectedoutput' => '1309521600'
2010             ),
2011             array(
2012                 'usertimezone' => '-10', // No dst applyed.
2013                 'year' => '2011',
2014                 'month' => '7',
2015                 'day' => '1',
2016                 'hour' => '10',
2017                 'minutes' => '00',
2018                 'seconds' => '00',
2019                 'timezone' => '2',  // Take this timezone.
2020                 'applydst' => true, // Apply dst.
2021                 'expectedoutput' => '1309507200'
2022             ),
2023             array(
2024                 'usertimezone' => '-10', // No dst applyed.
2025                 'year' => '2011',
2026                 'month' => '7',
2027                 'day' => '1',
2028                 'hour' => '10',
2029                 'minutes' => '00',
2030                 'seconds' => '00',
2031                 'timezone' => '-2', // Take this timezone.
2032                 'applydst' => true, // Apply dst.
2033                 'expectedoutput' => '1309521600'
2034             ),
2035             array(
2036                 'usertimezone' => '-10', // No dst applyed.
2037                 'year' => '2011',
2038                 'month' => '7',
2039                 'day' => '1',
2040                 'hour' => '10',
2041                 'minutes' => '00',
2042                 'seconds' => '00',
2043                 'timezone' => 'random/time', // This should show server time.
2044                 'applydst' => true,          // Apply dst.
2045                 'expectedoutput' => '1309485600'
2046             ),
2047             array(
2048                 'usertimezone' => '-14', // Server time.
2049                 'year' => '2011',
2050                 'month' => '7',
2051                 'day' => '1',
2052                 'hour' => '10',
2053                 'minutes' => '00',
2054                 'seconds' => '00',
2055                 'timezone' => '99', // Get user time.
2056                 'applydst' => true, // Apply dst.
2057                 'expectedoutput' => '1309485600'
2058             )
2059         );
2061         // Set default timezone to Australia/Perth, else time calculated
2062         // will not match expected values.
2063         $this->setTimezone(99, 'Australia/Perth');
2065         // Test make_timestamp with all testvals and assert if anything wrong.
2066         foreach ($testvalues as $vals) {
2067             $USER->timezone = $vals['usertimezone'];
2068             $actualoutput = make_timestamp(
2069                 $vals['year'],
2070                 $vals['month'],
2071                 $vals['day'],
2072                 $vals['hour'],
2073                 $vals['minutes'],
2074                 $vals['seconds'],
2075                 $vals['timezone'],
2076                 $vals['applydst']
2077             );
2079             // On different systems case of AM PM changes so compare case insensitive.
2080             $vals['expectedoutput'] = core_text::strtolower($vals['expectedoutput']);
2081             $actualoutput = core_text::strtolower($actualoutput);
2083             $this->assertSame($vals['expectedoutput'], $actualoutput,
2084                 "Expected: {$vals['expectedoutput']} => Actual: {$actualoutput},
2085                 Please check if timezones are updated (Site adminstration -> location -> update timezone)");
2086         }
2087     }
2089     /**
2090      * Test get_string and most importantly the implementation of the lang_string
2091      * object.
2092      */
2093     public function test_get_string() {
2094         global $COURSE;
2096         // Make sure we are using English.
2097         $originallang = $COURSE->lang;
2098         $COURSE->lang = 'en';
2100         $yes = get_string('yes');
2101         $yesexpected = 'Yes';
2102         $this->assertIsString($yes);
2103         $this->assertSame($yesexpected, $yes);
2105         $yes = get_string('yes', 'moodle');
2106         $this->assertIsString($yes);
2107         $this->assertSame($yesexpected, $yes);
2109         $yes = get_string('yes', 'core');
2110         $this->assertIsString($yes);
2111         $this->assertSame($yesexpected, $yes);
2113         $yes = get_string('yes', '');
2114         $this->assertIsString($yes);
2115         $this->assertSame($yesexpected, $yes);
2117         $yes = get_string('yes', null);
2118         $this->assertIsString($yes);
2119         $this->assertSame($yesexpected, $yes);
2121         $yes = get_string('yes', null, 1);
2122         $this->assertIsString($yes);
2123         $this->assertSame($yesexpected, $yes);
2125         $days = 1;
2126         $numdays = get_string('numdays', 'core', '1');
2127         $numdaysexpected = $days.' days';
2128         $this->assertIsString($numdays);
2129         $this->assertSame($numdaysexpected, $numdays);
2131         $yes = get_string('yes', null, null, true);
2132         $this->assertInstanceOf('lang_string', $yes);
2133         $this->assertSame($yesexpected, (string)$yes);
2135         // Test using a lang_string object as the $a argument for a normal
2136         // get_string call (returning string).
2137         $test = new lang_string('yes', null, null, true);
2138         $testexpected = get_string('numdays', 'core', get_string('yes'));
2139         $testresult = get_string('numdays', null, $test);
2140         $this->assertIsString($testresult);
2141         $this->assertSame($testexpected, $testresult);
2143         // Test using a lang_string object as the $a argument for an object
2144         // get_string call (returning lang_string).
2145         $test = new lang_string('yes', null, null, true);
2146         $testexpected = get_string('numdays', 'core', get_string('yes'));
2147         $testresult = get_string('numdays', null, $test, true);
2148         $this->assertInstanceOf('lang_string', $testresult);
2149         $this->assertSame($testexpected, "$testresult");
2151         // Make sure that object properties that can't be converted don't cause
2152         // errors.
2153         // Level one: This is as deep as current language processing goes.
2154         $test = new stdClass;
2155         $test->one = 'here';
2156         $string = get_string('yes', null, $test, true);
2157         $this->assertEquals($yesexpected, $string);
2159         // Make sure that object properties that can't be converted don't cause
2160         // errors.
2161         // Level two: Language processing doesn't currently reach this deep.
2162         // only immediate scalar properties are worked with.
2163         $test = new stdClass;
2164         $test->one = new stdClass;
2165         $test->one->two = 'here';
2166         $string = get_string('yes', null, $test, true);
2167         $this->assertEquals($yesexpected, $string);
2169         // Make sure that object properties that can't be converted don't cause
2170         // errors.
2171         // Level three: It should never ever go this deep, but we're making sure
2172         // it doesn't cause any probs anyway.
2173         $test = new stdClass;
2174         $test->one = new stdClass;
2175         $test->one->two = new stdClass;
2176         $test->one->two->three = 'here';
2177         $string = get_string('yes', null, $test, true);
2178         $this->assertEquals($yesexpected, $string);
2180         // Make sure that object properties that can't be converted don't cause
2181         // errors and check lang_string properties.
2182         // Level one: This is as deep as current language processing goes.
2183         $test = new stdClass;
2184         $test->one = new lang_string('yes');
2185         $string = get_string('yes', null, $test, true);
2186         $this->assertEquals($yesexpected, $string);
2188         // Make sure that object properties that can't be converted don't cause
2189         // errors and check lang_string properties.
2190         // Level two: Language processing doesn't currently reach this deep.
2191         // only immediate scalar properties are worked with.
2192         $test = new stdClass;
2193         $test->one = new stdClass;
2194         $test->one->two = new lang_string('yes');
2195         $string = get_string('yes', null, $test, true);
2196         $this->assertEquals($yesexpected, $string);
2198         // Make sure that object properties that can't be converted don't cause
2199         // errors and check lang_string properties.
2200         // Level three: It should never ever go this deep, but we're making sure
2201         // it doesn't cause any probs anyway.
2202         $test = new stdClass;
2203         $test->one = new stdClass;
2204         $test->one->two = new stdClass;
2205         $test->one->two->three = new lang_string('yes');
2206         $string = get_string('yes', null, $test, true);
2207         $this->assertEquals($yesexpected, $string);
2209         // Make sure that array properties that can't be converted don't cause
2210         // errors.
2211         $test = array();
2212         $test['one'] = new stdClass;
2213         $test['one']->two = 'here';
2214         $string = get_string('yes', null, $test, true);
2215         $this->assertEquals($yesexpected, $string);
2217         // Same thing but as above except using an object... this is allowed :P.
2218         $string = get_string('yes', null, null, true);
2219         $object = new stdClass;
2220         $object->$string = 'Yes';
2221         $this->assertEquals($yesexpected, $string);
2222         $this->assertEquals($yesexpected, $object->$string);
2224         // Reset the language.
2225         $COURSE->lang = $originallang;
2226     }
2228     public function test_get_string_limitation() {
2229         // This is one of the limitations to the lang_string class. It can't be
2230         // used as a key.
2231         $this->expectException(\PHPUnit\Framework\Error\Warning::class);
2232         $array = array(get_string('yes', null, null, true) => 'yes');
2233     }
2235     /**
2236      * Test localised float formatting.
2237      */
2238     public function test_format_float() {
2240         // Special case for null.
2241         $this->assertEquals('', format_float(null));
2243         // Default 1 decimal place.
2244         $this->assertEquals('5.4', format_float(5.43));
2245         $this->assertEquals('5.0', format_float(5.001));
2247         // Custom number of decimal places.
2248         $this->assertEquals('5.43000', format_float(5.43, 5));
2250         // Auto detect the number of decimal places.
2251         $this->assertEquals('5.43', format_float(5.43, -1));
2252         $this->assertEquals('5.43', format_float(5.43000, -1));
2253         $this->assertEquals('5', format_float(5, -1));
2254         $this->assertEquals('5', format_float(5.0, -1));
2255         $this->assertEquals('0.543', format_float('5.43e-1', -1));
2256         $this->assertEquals('0.543', format_float('5.43000e-1', -1));
2258         // Option to strip ending zeros after rounding.
2259         $this->assertEquals('5.43', format_float(5.43, 5, true, true));
2260         $this->assertEquals('5', format_float(5.0001, 3, true, true));
2262         // Tests with a localised decimal separator.
2263         $this->define_local_decimal_separator();
2265         // Localisation on (default).
2266         $this->assertEquals('5X43000', format_float(5.43, 5));
2267         $this->assertEquals('5X43', format_float(5.43, 5, true, true));
2269         // Localisation off.
2270         $this->assertEquals('5.43000', format_float(5.43, 5, false));
2271         $this->assertEquals('5.43', format_float(5.43, 5, false, true));
2273         // Tests with tilde as localised decimal separator.
2274         $this->define_local_decimal_separator('~');
2276         // Must also work for '~' as decimal separator.
2277         $this->assertEquals('5', format_float(5.0001, 3, true, true));
2278         $this->assertEquals('5~43000', format_float(5.43, 5));
2279         $this->assertEquals('5~43', format_float(5.43, 5, true, true));
2280     }
2282     /**
2283      * Test localised float unformatting.
2284      */
2285     public function test_unformat_float() {
2287         // Tests without the localised decimal separator.
2289         // Special case for null, empty or white spaces only strings.
2290         $this->assertEquals(null, unformat_float(null));
2291         $this->assertEquals(null, unformat_float(''));
2292         $this->assertEquals(null, unformat_float('    '));
2294         // Regular use.
2295         $this->assertEquals(5.4, unformat_float('5.4'));
2296         $this->assertEquals(5.4, unformat_float('5.4', true));
2298         // No decimal.
2299         $this->assertEquals(5.0, unformat_float('5'));
2301         // Custom number of decimal.
2302         $this->assertEquals(5.43267, unformat_float('5.43267'));
2304         // Empty decimal.
2305         $this->assertEquals(100.0, unformat_float('100.00'));
2307         // With the thousand separator.
2308         $this->assertEquals(1000.0, unformat_float('1 000'));
2309         $this->assertEquals(1000.32, unformat_float('1 000.32'));
2311         // Negative number.
2312         $this->assertEquals(-100.0, unformat_float('-100'));
2314         // Wrong value.
2315         $this->assertEquals(0.0, unformat_float('Wrong value'));
2316         // Wrong value in strict mode.
2317         $this->assertFalse(unformat_float('Wrong value', true));
2319         // Combining options.
2320         $this->assertEquals(-1023.862567, unformat_float('   -1 023.862567     '));
2322         // Bad decimal separator (should crop the decimal).
2323         $this->assertEquals(50.0, unformat_float('50,57'));
2324         // Bad decimal separator in strict mode (should return false).
2325         $this->assertFalse(unformat_float('50,57', true));
2327         // Tests with a localised decimal separator.
2328         $this->define_local_decimal_separator();
2330         // We repeat the tests above but with the current decimal separator.
2332         // Regular use without and with the localised separator.
2333         $this->assertEquals (5.4, unformat_float('5.4'));
2334         $this->assertEquals (5.4, unformat_float('5X4'));
2336         // Custom number of decimal.
2337         $this->assertEquals (5.43267, unformat_float('5X43267'));
2339         // Empty decimal.
2340         $this->assertEquals (100.0, unformat_float('100X00'));
2342         // With the thousand separator.
2343         $this->assertEquals (1000.32, unformat_float('1 000X32'));
2345         // Bad different separator (should crop the decimal).
2346         $this->assertEquals (50.0, unformat_float('50Y57'));
2347         // Bad different separator in strict mode (should return false).
2348         $this->assertFalse (unformat_float('50Y57', true));
2350         // Combining options.
2351         $this->assertEquals (-1023.862567, unformat_float('   -1 023X862567     '));
2352         // Combining options in strict mode.
2353         $this->assertEquals (-1023.862567, unformat_float('   -1 023X862567     ', true));
2354     }
2356     /**
2357      * Test deleting of users.
2358      */
2359     public function test_delete_user() {
2360         global $DB, $CFG;
2362         $this->resetAfterTest();
2364         $guest = $DB->get_record('user', array('id'=>$CFG->siteguest), '*', MUST_EXIST);
2365         $admin = $DB->get_record('user', array('id'=>$CFG->siteadmins), '*', MUST_EXIST);
2366         $this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
2368         $user = $this->getDataGenerator()->create_user(array('idnumber'=>'abc'));
2369         $user2 = $this->getDataGenerator()->create_user(array('idnumber'=>'xyz'));
2370         $usersharedemail1 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
2371         $usersharedemail2 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
2372         $useremptyemail1 = $this->getDataGenerator()->create_user(array('email' => ''));
2373         $useremptyemail2 = $this->getDataGenerator()->create_user(array('email' => ''));
2375         // Delete user and capture event.
2376         $sink = $this->redirectEvents();
2377         $result = delete_user($user);
2378         $events = $sink->get_events();
2379         $sink->close();
2380         $event = array_pop($events);
2382         // Test user is deleted in DB.
2383         $this->assertTrue($result);
2384         $deluser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
2385         $this->assertEquals(1, $deluser->deleted);
2386         $this->assertEquals(0, $deluser->picture);
2387         $this->assertSame('', $deluser->idnumber);
2388         $this->assertSame(md5($user->username), $deluser->email);
2389         $this->assertRegExp('/^'.preg_quote($user->email, '/').'\.\d*$/', $deluser->username);
2391         $this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
2393         // Test Event.
2394         $this->assertInstanceOf('\core\event\user_deleted', $event);
2395         $this->assertSame($user->id, $event->objectid);
2396         $this->assertSame('user_deleted', $event->get_legacy_eventname());
2397         $this->assertEventLegacyData($user, $event);
2398         $expectedlogdata = array(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
2399         $this->assertEventLegacyLogData($expectedlogdata, $event);
2400         $eventdata = $event->get_data();
2401         $this->assertSame($eventdata['other']['username'], $user->username);
2402         $this->assertSame($eventdata['other']['email'], $user->email);
2403         $this->assertSame($eventdata['other']['idnumber'], $user->idnumber);
2404         $this->assertSame($eventdata['other']['picture'], $user->picture);
2405         $this->assertSame($eventdata['other']['mnethostid'], $user->mnethostid);
2406         $this->assertEquals($user, $event->get_record_snapshot('user', $event->objectid));
2407         $this->assertEventContextNotUsed($event);
2409         // Try invalid params.
2410         $record = new stdClass();
2411         $record->grrr = 1;
2412         try {
2413             delete_user($record);
2414             $this->fail('Expecting exception for invalid delete_user() $user parameter');
2415         } catch (moodle_exception $ex) {
2416             $this->assertInstanceOf('coding_exception', $ex);
2417         }
2418         $record->id = 1;
2419         try {
2420             delete_user($record);
2421             $this->fail('Expecting exception for invalid delete_user() $user parameter');
2422         } catch (moodle_exception $ex) {
2423             $this->assertInstanceOf('coding_exception', $ex);
2424         }
2426         $record = new stdClass();
2427         $record->id = 666;
2428         $record->username = 'xx';
2429         $this->assertFalse($DB->record_exists('user', array('id'=>666))); // Any non-existent id is ok.
2430         $result = delete_user($record);
2431         $this->assertFalse($result);
2433         $result = delete_user($guest);
2434         $this->assertFalse($result);
2436         $result = delete_user($admin);
2437         $this->assertFalse($result);
2439         // Simultaneously deleting users with identical email addresses.
2440         $result1 = delete_user($usersharedemail1);
2441         $result2 = delete_user($usersharedemail2);
2443         $usersharedemail1after = $DB->get_record('user', array('id' => $usersharedemail1->id));
2444         $usersharedemail2after = $DB->get_record('user', array('id' => $usersharedemail2->id));
2445         $this->assertTrue($result1);
2446         $this->assertTrue($result2);
2447         $this->assertStringStartsWith($usersharedemail1->email . '.', $usersharedemail1after->username);
2448         $this->assertStringStartsWith($usersharedemail2->email . '.', $usersharedemail2after->username);
2450         // Simultaneously deleting users without email addresses.
2451         $result1 = delete_user($useremptyemail1);
2452         $result2 = delete_user($useremptyemail2);
2454         $useremptyemail1after = $DB->get_record('user', array('id' => $useremptyemail1->id));
2455         $useremptyemail2after = $DB->get_record('user', array('id' => $useremptyemail2->id));
2456         $this->assertTrue($result1);
2457         $this->assertTrue($result2);
2458         $this->assertStringStartsWith($useremptyemail1->username . '.' . $useremptyemail1->id . '@unknownemail.invalid.',
2459             $useremptyemail1after->username);
2460         $this->assertStringStartsWith($useremptyemail2->username . '.' . $useremptyemail2->id . '@unknownemail.invalid.',
2461             $useremptyemail2after->username);
2463         $this->resetDebugging();
2464     }
2466     /**
2467      * Test deletion of user with long username
2468      */
2469     public function test_delete_user_long_username() {
2470         global $DB;
2472         $this->resetAfterTest();
2474         // For users without an e-mail, one will be created during deletion using {$username}.{$id}@unknownemail.invalid format.
2475         $user = $this->getDataGenerator()->create_user([
2476             'username' => str_repeat('a', 75),
2477             'email' => '',
2478         ]);
2480         delete_user($user);
2482         // The username for the deleted user shouldn't exceed 100 characters.
2483         $usernamedeleted = $DB->get_field('user', 'username', ['id' => $user->id]);
2484         $this->assertEquals(100, core_text::strlen($usernamedeleted));
2486         $timestrlength = core_text::strlen((string) time());
2488         // It should start with the user name, and end with the current time.
2489         $this->assertStringStartsWith("{$user->username}.{$user->id}@", $usernamedeleted);
2490         $this->assertRegExp('/\.\d{' . $timestrlength . '}$/', $usernamedeleted);
2491     }
2493     /**
2494      * Test deletion of user with long email address
2495      */
2496     public function test_delete_user_long_email() {
2497         global $DB;
2499         $this->resetAfterTest();
2501         // Create user with 90 character email address.
2502         $user = $this->getDataGenerator()->create_user([
2503             'email' => str_repeat('a', 78) . '@example.com',
2504         ]);
2506         delete_user($user);
2508         // The username for the deleted user shouldn't exceed 100 characters.
2509         $usernamedeleted = $DB->get_field('user', 'username', ['id' => $user->id]);
2510         $this->assertEquals(100, core_text::strlen($usernamedeleted));
2512         $timestrlength = core_text::strlen((string) time());
2514         // Max username length is 100 chars. Select up to limit - (length of current time + 1 [period character]) from users email.
2515         $expectedemail = core_text::substr($user->email, 0, 100 - ($timestrlength + 1));
2516         $this->assertRegExp('/^' . preg_quote($expectedemail) . '\.\d{' . $timestrlength . '}$/', $usernamedeleted);
2517     }
2519     /**
2520      * Test function convert_to_array()
2521      */
2522     public function test_convert_to_array() {
2523         // Check that normal classes are converted to arrays the same way as (array) would do.
2524         $obj = new stdClass();
2525         $obj->prop1 = 'hello';
2526         $obj->prop2 = array('first', 'second', 13);
2527         $obj->prop3 = 15;
2528         $this->assertEquals(convert_to_array($obj), (array)$obj);
2530         // Check that context object (with iterator) is converted to array properly.
2531         $obj = context_system::instance();
2532         $ar = array(
2533             'id'           => $obj->id,
2534             'contextlevel' => $obj->contextlevel,
2535             'instanceid'   => $obj->instanceid,
2536             'path'         => $obj->path,
2537             'depth'        => $obj->depth,
2538             'locked'       => $obj->locked,
2539         );
2540         $this->assertEquals(convert_to_array($obj), $ar);
2541     }
2543     /**
2544      * Test the function date_format_string().
2545      */
2546     public function test_date_format_string() {
2547         global $CFG;
2549         $this->resetAfterTest();
2550         $this->setTimezone(99, 'Australia/Perth');
2552         $tests = array(
2553             array(
2554                 'tz' => 99,
2555                 'str' => '%A, %d %B %Y, %I:%M %p',
2556                 'expected' => 'Saturday, 01 January 2011, 06:00 PM'
2557             ),
2558             array(
2559                 'tz' => 0,
2560                 'str' => '%A, %d %B %Y, %I:%M %p',
2561                 'expected' => 'Saturday, 01 January 2011, 10:00 AM'
2562             ),
2563             array(
2564                 // Note: this function expected the timestamp in weird format before,
2565                 // since 2.9 it uses UTC.
2566                 'tz' => 'Pacific/Auckland',
2567                 'str' => '%A, %d %B %Y, %I:%M %p',
2568                 'expected' => 'Saturday, 01 January 2011, 11:00 PM'
2569             ),
2570             // Following tests pass on Windows only because en lang pack does
2571             // not contain localewincharset, in real life lang pack maintainers
2572             // may use only characters that are present in localewincharset
2573             // in format strings!
2574             array(
2575                 'tz' => 99,
2576                 'str' => 'Žluťoučký koníček %A',
2577                 'expected' => 'Žluťoučký koníček Saturday'
2578             ),
2579             array(
2580                 'tz' => 99,
2581                 'str' => '言語設定言語 %A',
2582                 'expected' => '言語設定言語 Saturday'
2583             ),
2584             array(
2585                 'tz' => 99,
2586                 'str' => '简体中文简体 %A',
2587                 'expected' => '简体中文简体 Saturday'
2588             ),
2589         );
2591         // Note: date_format_string() uses the timezone only to differenciate
2592         // the server time from the UTC time. It does not modify the timestamp.
2593         // Hence similar results for timezones <= 13.
2594         // On different systems case of AM PM changes so compare case insensitive.
2595         foreach ($tests as $test) {
2596             $str = date_format_string(1293876000, $test['str'], $test['tz']);
2597             $this->assertSame(core_text::strtolower($test['expected']), core_text::strtolower($str));
2598         }
2599     }
2601     public function test_get_config() {
2602         global $CFG;
2604         $this->resetAfterTest();
2606         // Preparation.
2607         set_config('phpunit_test_get_config_1', 'test 1');
2608         set_config('phpunit_test_get_config_2', 'test 2', 'mod_forum');
2609         if (!is_array($CFG->config_php_settings)) {
2610             $CFG->config_php_settings = array();
2611         }
2612         $CFG->config_php_settings['phpunit_test_get_config_3'] = 'test 3';
2614         if (!is_array($CFG->forced_plugin_settings)) {
2615             $CFG->forced_plugin_settings = array();
2616         }
2617         if (!array_key_exists('mod_forum', $CFG->forced_plugin_settings)) {
2618             $CFG->forced_plugin_settings['mod_forum'] = array();
2619         }
2620         $CFG->forced_plugin_settings['mod_forum']['phpunit_test_get_config_4'] = 'test 4';
2621         $CFG->phpunit_test_get_config_5 = 'test 5';
2623         // Testing.
2624         $this->assertSame('test 1', get_config('core', 'phpunit_test_get_config_1'));
2625         $this->assertSame('test 2', get_config('mod_forum', 'phpunit_test_get_config_2'));
2626         $this->assertSame('test 3', get_config('core', 'phpunit_test_get_config_3'));
2627         $this->assertSame('test 4', get_config('mod_forum', 'phpunit_test_get_config_4'));
2628         $this->assertFalse(get_config('core', 'phpunit_test_get_config_5'));
2629         $this->assertFalse(get_config('core', 'phpunit_test_get_config_x'));
2630         $this->assertFalse(get_config('mod_forum', 'phpunit_test_get_config_x'));
2632         // Test config we know to exist.
2633         $this->assertSame($CFG->dataroot, get_config('core', 'dataroot'));
2634         $this->assertSame($CFG->phpunit_dataroot, get_config('core', 'phpunit_dataroot'));
2635         $this->assertSame($CFG->dataroot, get_config('core', 'phpunit_dataroot'));
2636         $this->assertSame(get_config('core', 'dataroot'), get_config('core', 'phpunit_dataroot'));
2638         // Test setting a config var that already exists.
2639         set_config('phpunit_test_get_config_1', 'test a');
2640         $this->assertSame('test a', $CFG->phpunit_test_get_config_1);
2641         $this->assertSame('test a', get_config('core', 'phpunit_test_get_config_1'));
2643         // Test cache invalidation.
2644         $cache = cache::make('core', 'config');
2645         $this->assertIsArray($cache->get('core'));
2646         $this->assertIsArray($cache->get('mod_forum'));
2647         set_config('phpunit_test_get_config_1', 'test b');
2648         $this->assertFalse($cache->get('core'));
2649         set_config('phpunit_test_get_config_4', 'test c', 'mod_forum');
2650         $this->assertFalse($cache->get('mod_forum'));
2651     }
2653     public function test_get_max_upload_sizes() {
2654         // Test with very low limits so we are not affected by php upload limits.
2655         // Test activity limit smallest.
2656         $sitebytes = 102400;
2657         $coursebytes = 51200;
2658         $modulebytes = 10240;
2659         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2661         $this->assertSame('Activity upload limit (10KB)', $result['0']);
2662         $this->assertCount(2, $result);
2664         // Test course limit smallest.
2665         $sitebytes = 102400;
2666         $coursebytes = 10240;
2667         $modulebytes = 51200;
2668         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2670         $this->assertSame('Course upload limit (10KB)', $result['0']);
2671         $this->assertCount(2, $result);
2673         // Test site limit smallest.
2674         $sitebytes = 10240;
2675         $coursebytes = 102400;
2676         $modulebytes = 51200;
2677         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2679         $this->assertSame('Site upload limit (10KB)', $result['0']);
2680         $this->assertCount(2, $result);
2682         // Test site limit not set.
2683         $sitebytes = 0;
2684         $coursebytes = 102400;
2685         $modulebytes = 51200;
2686         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2688         $this->assertSame('Activity upload limit (50KB)', $result['0']);
2689         $this->assertCount(3, $result);
2691         $sitebytes = 0;
2692         $coursebytes = 51200;
2693         $modulebytes = 102400;
2694         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2696         $this->assertSame('Course upload limit (50KB)', $result['0']);
2697         $this->assertCount(3, $result);
2699         // Test custom bytes in range.
2700         $sitebytes = 102400;
2701         $coursebytes = 51200;
2702         $modulebytes = 51200;
2703         $custombytes = 10240;
2704         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2706         $this->assertCount(3, $result);
2708         // Test custom bytes in range but non-standard.
2709         $sitebytes = 102400;
2710         $coursebytes = 51200;
2711         $modulebytes = 51200;
2712         $custombytes = 25600;
2713         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2715         $this->assertCount(4, $result);
2717         // Test custom bytes out of range.
2718         $sitebytes = 102400;
2719         $coursebytes = 51200;
2720         $modulebytes = 51200;
2721         $custombytes = 102400;
2722         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2724         $this->assertCount(3, $result);
2726         // Test custom bytes out of range and non-standard.
2727         $sitebytes = 102400;
2728         $coursebytes = 51200;
2729         $modulebytes = 51200;
2730         $custombytes = 256000;
2731         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2733         $this->assertCount(3, $result);
2735         // Test site limit only.
2736         $sitebytes = 51200;
2737         $result = get_max_upload_sizes($sitebytes);
2739         $this->assertSame('Site upload limit (50KB)', $result['0']);
2740         $this->assertSame('50KB', $result['51200']);
2741         $this->assertSame('10KB', $result['10240']);
2742         $this->assertCount(3, $result);
2744         // Test no limit.
2745         $result = get_max_upload_sizes();
2746         $this->assertArrayHasKey('0', $result);
2747         $this->assertArrayHasKey(get_max_upload_file_size(), $result);
2748     }
2750     /**
2751      * Test function password_is_legacy_hash().
2752      */
2753     public function test_password_is_legacy_hash() {
2754         // Well formed md5s should be matched.
2755         foreach (array('some', 'strings', 'to_check!') as $string) {
2756             $md5 = md5($string);
2757             $this->assertTrue(password_is_legacy_hash($md5));
2758         }
2759         // Strings that are not md5s should not be matched.
2760         foreach (array('', AUTH_PASSWORD_NOT_CACHED, 'IPW8WTcsWNgAWcUS1FBVHegzJnw5M2jOmYkmfc8z.xdBOyC4Caeum') as $notmd5) {
2761             $this->assertFalse(password_is_legacy_hash($notmd5));
2762         }
2763     }
2765     /**
2766      * Test function validate_internal_user_password().
2767      */
2768     public function test_validate_internal_user_password() {
2769         // Test bcrypt hashes.
2770         $validhashes = array(
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         );
2777         foreach ($validhashes as $password => $hash) {
2778             $user = new stdClass();
2779             $user->auth = 'manual';
2780             $user->password = $hash;
2781             // The correct password should be validated.
2782             $this->assertTrue(validate_internal_user_password($user, $password));
2783             // An incorrect password should not be validated.
2784             $this->assertFalse(validate_internal_user_password($user, 'badpw'));
2785         }
2786     }
2788     /**
2789      * Test function hash_internal_user_password().
2790      */
2791     public function test_hash_internal_user_password() {
2792         $passwords = array('pw', 'abc123', 'C0mP1eX_&}<?@*&%` |\"', 'ĩńťėŕňăţĩōŋāĹ');
2794         // Check that some passwords that we convert to hashes can
2795         // be validated.
2796         foreach ($passwords as $password) {
2797             $hash = hash_internal_user_password($password);
2798             $fasthash = hash_internal_user_password($password, true);
2799             $user = new stdClass();
2800             $user->auth = 'manual';
2801             $user->password = $hash;
2802             $this->assertTrue(validate_internal_user_password($user, $password));
2804             // They should not be in md5 format.
2805             $this->assertFalse(password_is_legacy_hash($hash));
2807             // Check that cost factor in hash is correctly set.
2808             $this->assertRegExp('/\$10\$/', $hash);
2809             $this->assertRegExp('/\$04\$/', $fasthash);
2810         }
2811     }
2813     /**
2814      * Test function update_internal_user_password().
2815      */
2816     public function test_update_internal_user_password() {
2817         global $DB;
2818         $this->resetAfterTest();
2819         $passwords = array('password', '1234', 'changeme', '****');
2820         foreach ($passwords as $password) {
2821             $user = $this->getDataGenerator()->create_user(array('auth'=>'manual'));
2822             update_internal_user_password($user, $password);
2823             // The user object should have been updated.
2824             $this->assertTrue(validate_internal_user_password($user, $password));
2825             // The database field for the user should also have been updated to the
2826             // same value.
2827             $this->assertSame($user->password, $DB->get_field('user', 'password', array('id' => $user->id)));
2828         }
2830         $user = $this->getDataGenerator()->create_user(array('auth'=>'manual'));
2831         // Manually set the user's password to the md5 of the string 'password'.
2832         $DB->set_field('user', 'password', '5f4dcc3b5aa765d61d8327deb882cf99', array('id' => $user->id));
2834         $sink = $this->redirectEvents();
2835         // Update the password.
2836         update_internal_user_password($user, 'password');
2837         $events = $sink->get_events();
2838         $sink->close();
2839         $event = array_pop($events);
2841         // Password should have been updated to a bcrypt hash.
2842         $this->assertFalse(password_is_legacy_hash($user->password));
2844         // Verify event information.
2845         $this->assertInstanceOf('\core\event\user_password_updated', $event);
2846         $this->assertSame($user->id, $event->relateduserid);
2847         $this->assertEquals(context_user::instance($user->id), $event->get_context());
2848         $this->assertEventContextNotUsed($event);
2850         // Verify recovery of property 'auth'.
2851         unset($user->auth);
2852         update_internal_user_password($user, 'newpassword');
2853         $this->assertDebuggingCalled('User record in update_internal_user_password() must include field auth',
2854                 DEBUG_DEVELOPER);
2855         $this->assertEquals('manual', $user->auth);
2856     }
2858     /**
2859      * Testing that if the password is not cached, that it does not update
2860      * the user table and fire event.
2861      */
2862     public function test_update_internal_user_password_no_cache() {
2863         global $DB;
2864         $this->resetAfterTest();
2866         $user = $this->getDataGenerator()->create_user(array('auth' => 'cas'));
2867         $DB->update_record('user', ['id' => $user->id, 'password' => AUTH_PASSWORD_NOT_CACHED]);
2868         $user->password = AUTH_PASSWORD_NOT_CACHED;
2870         $sink = $this->redirectEvents();
2871         update_internal_user_password($user, 'wonkawonka');
2872         $this->assertEquals(0, $sink->count(), 'User updated event should not fire');
2873     }
2875     /**
2876      * Test if the user has a password hash, but now their auth method
2877      * says not to cache it.  Then it should update.
2878      */
2879     public function test_update_internal_user_password_update_no_cache() {
2880         $this->resetAfterTest();
2882         $user = $this->getDataGenerator()->create_user(array('password' => 'test'));
2883         $this->assertNotEquals(AUTH_PASSWORD_NOT_CACHED, $user->password);
2884         $user->auth = 'cas'; // Change to a auth that does not store passwords.
2886         $sink = $this->redirectEvents();
2887         update_internal_user_password($user, 'wonkawonka');
2888         $this->assertGreaterThanOrEqual(1, $sink->count(), 'User updated event should fire');
2890         $this->assertEquals(AUTH_PASSWORD_NOT_CACHED, $user->password);
2891     }
2893     public function test_fullname() {
2894         global $CFG;
2896         $this->resetAfterTest();
2898         // Create a user to test the name display on.
2899         $record = array();
2900         $record['firstname'] = 'Scott';
2901         $record['lastname'] = 'Fletcher';
2902         $record['firstnamephonetic'] = 'スコット';
2903         $record['lastnamephonetic'] = 'フレチャー';
2904         $record['alternatename'] = 'No friends';
2905         $user = $this->getDataGenerator()->create_user($record);
2907         // Back up config settings for restore later.
2908         $originalcfg = new stdClass();
2909         $originalcfg->fullnamedisplay = $CFG->fullnamedisplay;
2910         $originalcfg->alternativefullnameformat = $CFG->alternativefullnameformat;
2912         // Testing existing fullnamedisplay settings.
2913         $CFG->fullnamedisplay = 'firstname';
2914         $testname = fullname($user);
2915         $this->assertSame($user->firstname, $testname);
2917         $CFG->fullnamedisplay = 'firstname lastname';
2918         $expectedname = "$user->firstname $user->lastname";
2919         $testname = fullname($user);
2920         $this->assertSame($expectedname, $testname);
2922         $CFG->fullnamedisplay = 'lastname firstname';
2923         $expectedname = "$user->lastname $user->firstname";
2924         $testname = fullname($user);
2925         $this->assertSame($expectedname, $testname);
2927         $expectedname = get_string('fullnamedisplay', null, $user);
2928         $CFG->fullnamedisplay = 'language';
2929         $testname = fullname($user);
2930         $this->assertSame($expectedname, $testname);
2932         // Test override parameter.
2933         $CFG->fullnamedisplay = 'firstname';
2934         $expectedname = "$user->firstname $user->lastname";
2935         $testname = fullname($user, true);
2936         $this->assertSame($expectedname, $testname);
2938         // Test alternativefullnameformat setting.
2939         // Test alternativefullnameformat that has been set to nothing.
2940         $CFG->alternativefullnameformat = '';
2941         $expectedname = "$user->firstname $user->lastname";
2942         $testname = fullname($user, true);
2943         $this->assertSame($expectedname, $testname);
2945         // Test alternativefullnameformat that has been set to 'language'.
2946         $CFG->alternativefullnameformat = 'language';
2947         $expectedname = "$user->firstname $user->lastname";
2948         $testname = fullname($user, true);
2949         $this->assertSame($expectedname, $testname);
2951         // Test customising the alternativefullnameformat setting with all additional name fields.
2952         $CFG->alternativefullnameformat = 'firstname lastname firstnamephonetic lastnamephonetic middlename alternatename';
2953         $expectedname = "$user->firstname $user->lastname $user->firstnamephonetic $user->lastnamephonetic $user->middlename $user->alternatename";
2954         $testname = fullname($user, true);
2955         $this->assertSame($expectedname, $testname);
2957         // Test additional name fields.
2958         $CFG->fullnamedisplay = 'lastname lastnamephonetic firstname firstnamephonetic';
2959         $expectedname = "$user->lastname $user->lastnamephonetic $user->firstname $user->firstnamephonetic";
2960         $testname = fullname($user);
2961         $this->assertSame($expectedname, $testname);
2963         // Test for handling missing data.
2964         $user->middlename = null;
2965         // Parenthesis with no data.
2966         $CFG->fullnamedisplay = 'firstname (middlename) lastname';
2967         $expectedname = "$user->firstname $user->lastname";
2968         $testname = fullname($user);
2969         $this->assertSame($expectedname, $testname);
2971         // Extra spaces due to no data.
2972         $CFG->fullnamedisplay = 'firstname middlename lastname';
2973         $expectedname = "$user->firstname $user->lastname";
2974         $testname = fullname($user);
2975         $this->assertSame($expectedname, $testname);
2977         // Regular expression testing.
2978         // Remove some data from the user fields.
2979         $user->firstnamephonetic = '';
2980         $user->lastnamephonetic = '';
2982         // Removing empty brackets and excess whitespace.
2983         // All of these configurations should resolve to just firstname lastname.
2984         $configarray = array();
2985         $configarray[] = 'firstname lastname [firstnamephonetic lastnamephonetic]';
2986         $configarray[] = 'firstname lastname \'middlename\'';
2987         $configarray[] = 'firstname "firstnamephonetic" lastname';
2988         $configarray[] = 'firstname 「firstnamephonetic」 lastname 「lastnamephonetic」';
2990         foreach ($configarray as $config) {
2991             $CFG->fullnamedisplay = $config;
2992             $expectedname = "$user->firstname $user->lastname";
2993             $testname = fullname($user);
2994             $this->assertSame($expectedname, $testname);
2995         }
2997         // Check to make sure that other characters are left in place.
2998         $configarray = array();
2999         $configarray['0'] = new stdClass();
3000         $configarray['0']->config = 'lastname firstname, middlename';
3001         $configarray['0']->expectedname = "$user->lastname $user->firstname,";
3002         $configarray['1'] = new stdClass();
3003         $configarray['1']->config = 'lastname firstname + alternatename';
3004         $configarray['1']->expectedname = "$user->lastname $user->firstname + $user->alternatename";
3005         $configarray['2'] = new stdClass();
3006         $configarray['2']->config = 'firstname aka: alternatename';
3007         $configarray['2']->expectedname = "$user->firstname aka: $user->alternatename";
3008         $configarray['3'] = new stdClass();
3009         $configarray['3']->config = 'firstname (alternatename)';
3010         $configarray['3']->expectedname = "$user->firstname ($user->alternatename)";
3011         $configarray['4'] = new stdClass();
3012         $configarray['4']->config = 'firstname [alternatename]';
3013         $configarray['4']->expectedname = "$user->firstname [$user->alternatename]";
3014         $configarray['5'] = new stdClass();
3015         $configarray['5']->config = 'firstname "lastname"';
3016         $configarray['5']->expectedname = "$user->firstname \"$user->lastname\"";
3018         foreach ($configarray as $config) {
3019             $CFG->fullnamedisplay = $config->config;
3020             $expectedname = $config->expectedname;
3021             $testname = fullname($user);
3022             $this->assertSame($expectedname, $testname);
3023         }
3025         // Test debugging message displays when
3026         // fullnamedisplay setting is "normal".
3027         $CFG->fullnamedisplay = 'firstname lastname';
3028         unset($user);
3029         $user = new stdClass();
3030         $user->firstname = 'Stan';
3031         $user->lastname = 'Lee';
3032         $namedisplay = fullname($user);
3033         $this->assertDebuggingCalled();
3035         // Tidy up after we finish testing.
3036         $CFG->fullnamedisplay = $originalcfg->fullnamedisplay;
3037         $CFG->alternativefullnameformat = $originalcfg->alternativefullnameformat;
3038     }
3040     public function test_get_all_user_name_fields() {
3041         $this->resetAfterTest();
3043         // Additional names in an array.
3044         $testarray = array('firstnamephonetic' => 'firstnamephonetic',
3045                 'lastnamephonetic' => 'lastnamephonetic',
3046                 'middlename' => 'middlename',
3047                 'alternatename' => 'alternatename',
3048                 'firstname' => 'firstname',
3049                 'lastname' => 'lastname');
3050         $this->assertEquals($testarray, get_all_user_name_fields());
3052         // Additional names as a string.
3053         $teststring = 'firstnamephonetic,lastnamephonetic,middlename,alternatename,firstname,lastname';
3054         $this->assertEquals($teststring, get_all_user_name_fields(true));
3056         // Additional names as a string with an alias.
3057         $teststring = 't.firstnamephonetic,t.lastnamephonetic,t.middlename,t.alternatename,t.firstname,t.lastname';
3058         $this->assertEquals($teststring, get_all_user_name_fields(true, 't'));
3060         // Additional name fields with a prefix - object.
3061         $testarray = array('firstnamephonetic' => 'authorfirstnamephonetic',
3062                 'lastnamephonetic' => 'authorlastnamephonetic',
3063                 'middlename' => 'authormiddlename',
3064                 'alternatename' => 'authoralternatename',
3065                 'firstname' => 'authorfirstname',
3066                 'lastname' => 'authorlastname');
3067         $this->assertEquals($testarray, get_all_user_name_fields(false, null, 'author'));
3069         // Additional name fields with an alias and a title - string.
3070         $teststring = 'u.firstnamephonetic AS authorfirstnamephonetic,u.lastnamephonetic AS authorlastnamephonetic,u.middlename AS authormiddlename,u.alternatename AS authoralternatename,u.firstname AS authorfirstname,u.lastname AS authorlastname';
3071         $this->assertEquals($teststring, get_all_user_name_fields(true, 'u', null, 'author'));
3073         // Test the order parameter of the function.
3074         // Returning an array.
3075         $testarray = array('firstname' => 'firstname',
3076                 'lastname' => 'lastname',
3077                 'firstnamephonetic' => 'firstnamephonetic',
3078                 'lastnamephonetic' => 'lastnamephonetic',
3079                 'middlename' => 'middlename',
3080                 'alternatename' => 'alternatename'
3081         );
3082         $this->assertEquals($testarray, get_all_user_name_fields(false, null, null, null, true));
3084         // Returning a string.
3085         $teststring = 'firstname,lastname,firstnamephonetic,lastnamephonetic,middlename,alternatename';
3086         $this->assertEquals($teststring, get_all_user_name_fields(true, null, null, null, true));
3087     }
3089     public function test_order_in_string() {
3090         $this->resetAfterTest();
3092         // Return an array in an order as they are encountered in a string.
3093         $valuearray = array('second', 'firsthalf', 'first');
3094         $formatstring = 'first firsthalf some other text (second)';
3095         $expectedarray = array('0' => 'first', '6' => 'firsthalf', '33' => 'second');
3096         $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3098         // Try again with a different order for the format.
3099         $valuearray = array('second', 'firsthalf', 'first');
3100         $formatstring = 'firsthalf first second';
3101         $expectedarray = array('0' => 'firsthalf', '10' => 'first', '16' => 'second');
3102         $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3104         // Try again with yet another different order for the format.
3105         $valuearray = array('second', 'firsthalf', 'first');
3106         $formatstring = 'start seconds away second firstquater first firsthalf';
3107         $expectedarray = array('19' => 'second', '38' => 'first', '44' => 'firsthalf');
3108         $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3109     }
3111     public function test_complete_user_login() {
3112         global $USER, $DB;
3114         $this->resetAfterTest();
3115         $user = $this->getDataGenerator()->create_user();
3116         $this->setUser(0);
3118         $sink = $this->redirectEvents();
3119         $loginuser = clone($user);
3120         $this->setCurrentTimeStart();
3121         @complete_user_login($loginuser); // Hide session header errors.
3122         $this->assertSame($loginuser, $USER);
3123         $this->assertEquals($user->id, $USER->id);
3124         $events = $sink->get_events();
3125         $sink->close();
3127         $this->assertCount(1, $events);
3128         $event = reset($events);
3129         $this->assertInstanceOf('\core\event\user_loggedin', $event);
3130         $this->assertEquals('user', $event->objecttable);
3131         $this->assertEquals($user->id, $event->objectid);
3132         $this->assertEquals(context_system::instance()->id, $event->contextid);
3133         $this->assertEventContextNotUsed($event);
3135         $user = $DB->get_record('user', array('id'=>$user->id));
3137         $this->assertTimeCurrent($user->firstaccess);
3138         $this->assertTimeCurrent($user->lastaccess);
3140         $this->assertTimeCurrent($USER->firstaccess);
3141         $this->assertTimeCurrent($USER->lastaccess);
3142         $this->assertTimeCurrent($USER->currentlogin);
3143         $this->assertSame(sesskey(), $USER->sesskey);
3144         $this->assertTimeCurrent($USER->preference['_lastloaded']);
3145         $this->assertObjectNotHasAttribute('password', $USER);
3146         $this->assertObjectNotHasAttribute('description', $USER);
3147     }
3149     /**
3150      * Test require_logout.
3151      */
3152     public function test_require_logout() {
3153         $this->resetAfterTest();
3154         $user = $this->getDataGenerator()->create_user();
3155         $this->setUser($user);
3157         $this->assertTrue(isloggedin());
3159         // Logout user and capture event.
3160         $sink = $this->redirectEvents();
3161         require_logout();
3162         $events = $sink->get_events();
3163         $sink->close();
3164         $event = array_pop($events);
3166         // Check if user is logged out.
3167         $this->assertFalse(isloggedin());
3169         // Test Event.
3170         $this->assertInstanceOf('\core\event\user_loggedout', $event);
3171         $this->assertSame($user->id, $event->objectid);
3172         $this->assertSame('user_logout', $event->get_legacy_eventname());
3173         $this->assertEventLegacyData($user, $event);
3174         $expectedlogdata = array(SITEID, 'user', 'logout', 'view.php?id='.$event->objectid.'&course='.SITEID, $event->objectid, 0,
3175             $event->objectid);
3176         $this->assertEventLegacyLogData($expectedlogdata, $event);
3177         $this->assertEventContextNotUsed($event);
3178     }
3180     /**
3181      * A data provider for testing email messageid
3182      */
3183     public function generate_email_messageid_provider() {
3184         return array(
3185             'nopath' => array(
3186                 'wwwroot' => 'http://www.example.com',
3187                 'ids' => array(
3188                     'a-custom-id' => '<a-custom-id@www.example.com>',
3189                     'an-id-with-/-a-slash' => '<an-id-with-%2F-a-slash@www.example.com>',
3190                 ),
3191             ),
3192             'path' => array(
3193                 'wwwroot' => 'http://www.example.com/path/subdir',
3194                 'ids' => array(
3195                     'a-custom-id' => '<a-custom-id/path/subdir@www.example.com>',
3196                     'an-id-with-/-a-slash' => '<an-id-with-%2F-a-slash/path/subdir@www.example.com>',
3197                 ),
3198             ),
3199         );
3200     }
3202     /**
3203      * Test email message id generation
3204      *
3205      * @dataProvider generate_email_messageid_provider
3206      *
3207      * @param string $wwwroot The wwwroot
3208      * @param array $msgids An array of msgid local parts and the final result
3209      */
3210     public function test_generate_email_messageid($wwwroot, $msgids) {
3211         global $CFG;
3213         $this->resetAfterTest();
3214         $CFG->wwwroot = $wwwroot;
3216         foreach ($msgids as $local => $final) {
3217             $this->assertEquals($final, generate_email_messageid($local));
3218         }
3219     }
3221     /**
3222      * Test email with custom headers
3223      */
3224     public function test_send_email_with_custom_header() {
3225         global $DB, $CFG;