86a97c5bee772fa8665622f8a4e2c1d34d1b5b45
[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('', 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->assertInternalType('string', $yes);
2103         $this->assertSame($yesexpected, $yes);
2105         $yes = get_string('yes', 'moodle');
2106         $this->assertInternalType('string', $yes);
2107         $this->assertSame($yesexpected, $yes);
2109         $yes = get_string('yes', 'core');
2110         $this->assertInternalType('string', $yes);
2111         $this->assertSame($yesexpected, $yes);
2113         $yes = get_string('yes', '');
2114         $this->assertInternalType('string', $yes);
2115         $this->assertSame($yesexpected, $yes);
2117         $yes = get_string('yes', null);
2118         $this->assertInternalType('string', $yes);
2119         $this->assertSame($yesexpected, $yes);
2121         $yes = get_string('yes', null, 1);
2122         $this->assertInternalType('string', $yes);
2123         $this->assertSame($yesexpected, $yes);
2125         $days = 1;
2126         $numdays = get_string('numdays', 'core', '1');
2127         $numdaysexpected = $days.' days';
2128         $this->assertInternalType('string', $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->assertInternalType('string', $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     /**
2229      * @expectedException PHPUnit\Framework\Error\Warning
2230      */
2231     public function test_get_string_limitation() {
2232         // This is one of the limitations to the lang_string class. It can't be
2233         // used as a key.
2234         $array = array(get_string('yes', null, null, true) => 'yes');
2235     }
2237     /**
2238      * Test localised float formatting.
2239      */
2240     public function test_format_float() {
2242         // Special case for null.
2243         $this->assertEquals('', format_float(null));
2245         // Default 1 decimal place.
2246         $this->assertEquals('5.4', format_float(5.43));
2247         $this->assertEquals('5.0', format_float(5.001));
2249         // Custom number of decimal places.
2250         $this->assertEquals('5.43000', format_float(5.43, 5));
2252         // Option to strip ending zeros after rounding.
2253         $this->assertEquals('5.43', format_float(5.43, 5, true, true));
2254         $this->assertEquals('5', format_float(5.0001, 3, true, true));
2256         // Tests with a localised decimal separator.
2257         $this->define_local_decimal_separator();
2259         // Localisation on (default).
2260         $this->assertEquals('5X43000', format_float(5.43, 5));
2261         $this->assertEquals('5X43', format_float(5.43, 5, true, true));
2263         // Localisation off.
2264         $this->assertEquals('5.43000', format_float(5.43, 5, false));
2265         $this->assertEquals('5.43', format_float(5.43, 5, false, true));
2267         // Tests with tilde as localised decimal separator.
2268         $this->define_local_decimal_separator('~');
2270         // Must also work for '~' as decimal separator.
2271         $this->assertEquals('5', format_float(5.0001, 3, true, true));
2272         $this->assertEquals('5~43000', format_float(5.43, 5));
2273         $this->assertEquals('5~43', format_float(5.43, 5, true, true));
2274     }
2276     /**
2277      * Test localised float unformatting.
2278      */
2279     public function test_unformat_float() {
2281         // Tests without the localised decimal separator.
2283         // Special case for null, empty or white spaces only strings.
2284         $this->assertEquals(null, unformat_float(null));
2285         $this->assertEquals(null, unformat_float(''));
2286         $this->assertEquals(null, unformat_float('    '));
2288         // Regular use.
2289         $this->assertEquals(5.4, unformat_float('5.4'));
2290         $this->assertEquals(5.4, unformat_float('5.4', true));
2292         // No decimal.
2293         $this->assertEquals(5.0, unformat_float('5'));
2295         // Custom number of decimal.
2296         $this->assertEquals(5.43267, unformat_float('5.43267'));
2298         // Empty decimal.
2299         $this->assertEquals(100.0, unformat_float('100.00'));
2301         // With the thousand separator.
2302         $this->assertEquals(1000.0, unformat_float('1 000'));
2303         $this->assertEquals(1000.32, unformat_float('1 000.32'));
2305         // Negative number.
2306         $this->assertEquals(-100.0, unformat_float('-100'));
2308         // Wrong value.
2309         $this->assertEquals(0.0, unformat_float('Wrong value'));
2310         // Wrong value in strict mode.
2311         $this->assertFalse(unformat_float('Wrong value', true));
2313         // Combining options.
2314         $this->assertEquals(-1023.862567, unformat_float('   -1 023.862567     '));
2316         // Bad decimal separator (should crop the decimal).
2317         $this->assertEquals(50.0, unformat_float('50,57'));
2318         // Bad decimal separator in strict mode (should return false).
2319         $this->assertFalse(unformat_float('50,57', true));
2321         // Tests with a localised decimal separator.
2322         $this->define_local_decimal_separator();
2324         // We repeat the tests above but with the current decimal separator.
2326         // Regular use without and with the localised separator.
2327         $this->assertEquals (5.4, unformat_float('5.4'));
2328         $this->assertEquals (5.4, unformat_float('5X4'));
2330         // Custom number of decimal.
2331         $this->assertEquals (5.43267, unformat_float('5X43267'));
2333         // Empty decimal.
2334         $this->assertEquals (100.0, unformat_float('100X00'));
2336         // With the thousand separator.
2337         $this->assertEquals (1000.32, unformat_float('1 000X32'));
2339         // Bad different separator (should crop the decimal).
2340         $this->assertEquals (50.0, unformat_float('50Y57'));
2341         // Bad different separator in strict mode (should return false).
2342         $this->assertFalse (unformat_float('50Y57', true));
2344         // Combining options.
2345         $this->assertEquals (-1023.862567, unformat_float('   -1 023X862567     '));
2346         // Combining options in strict mode.
2347         $this->assertEquals (-1023.862567, unformat_float('   -1 023X862567     ', true));
2348     }
2350     /**
2351      * Test deleting of users.
2352      */
2353     public function test_delete_user() {
2354         global $DB, $CFG;
2356         $this->resetAfterTest();
2358         $guest = $DB->get_record('user', array('id'=>$CFG->siteguest), '*', MUST_EXIST);
2359         $admin = $DB->get_record('user', array('id'=>$CFG->siteadmins), '*', MUST_EXIST);
2360         $this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
2362         $user = $this->getDataGenerator()->create_user(array('idnumber'=>'abc'));
2363         $user2 = $this->getDataGenerator()->create_user(array('idnumber'=>'xyz'));
2364         $usersharedemail1 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
2365         $usersharedemail2 = $this->getDataGenerator()->create_user(array('email' => 'sharedemail@example.invalid'));
2366         $useremptyemail1 = $this->getDataGenerator()->create_user(array('email' => ''));
2367         $useremptyemail2 = $this->getDataGenerator()->create_user(array('email' => ''));
2369         // Delete user and capture event.
2370         $sink = $this->redirectEvents();
2371         $result = delete_user($user);
2372         $events = $sink->get_events();
2373         $sink->close();
2374         $event = array_pop($events);
2376         // Test user is deleted in DB.
2377         $this->assertTrue($result);
2378         $deluser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
2379         $this->assertEquals(1, $deluser->deleted);
2380         $this->assertEquals(0, $deluser->picture);
2381         $this->assertSame('', $deluser->idnumber);
2382         $this->assertSame(md5($user->username), $deluser->email);
2383         $this->assertRegExp('/^'.preg_quote($user->email, '/').'\.\d*$/', $deluser->username);
2385         $this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
2387         // Test Event.
2388         $this->assertInstanceOf('\core\event\user_deleted', $event);
2389         $this->assertSame($user->id, $event->objectid);
2390         $this->assertSame('user_deleted', $event->get_legacy_eventname());
2391         $this->assertEventLegacyData($user, $event);
2392         $expectedlogdata = array(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
2393         $this->assertEventLegacyLogData($expectedlogdata, $event);
2394         $eventdata = $event->get_data();
2395         $this->assertSame($eventdata['other']['username'], $user->username);
2396         $this->assertSame($eventdata['other']['email'], $user->email);
2397         $this->assertSame($eventdata['other']['idnumber'], $user->idnumber);
2398         $this->assertSame($eventdata['other']['picture'], $user->picture);
2399         $this->assertSame($eventdata['other']['mnethostid'], $user->mnethostid);
2400         $this->assertEquals($user, $event->get_record_snapshot('user', $event->objectid));
2401         $this->assertEventContextNotUsed($event);
2403         // Try invalid params.
2404         $record = new stdClass();
2405         $record->grrr = 1;
2406         try {
2407             delete_user($record);
2408             $this->fail('Expecting exception for invalid delete_user() $user parameter');
2409         } catch (moodle_exception $ex) {
2410             $this->assertInstanceOf('coding_exception', $ex);
2411         }
2412         $record->id = 1;
2413         try {
2414             delete_user($record);
2415             $this->fail('Expecting exception for invalid delete_user() $user parameter');
2416         } catch (moodle_exception $ex) {
2417             $this->assertInstanceOf('coding_exception', $ex);
2418         }
2420         $record = new stdClass();
2421         $record->id = 666;
2422         $record->username = 'xx';
2423         $this->assertFalse($DB->record_exists('user', array('id'=>666))); // Any non-existent id is ok.
2424         $result = delete_user($record);
2425         $this->assertFalse($result);
2427         $result = delete_user($guest);
2428         $this->assertFalse($result);
2430         $result = delete_user($admin);
2431         $this->assertFalse($result);
2433         // Simultaneously deleting users with identical email addresses.
2434         $result1 = delete_user($usersharedemail1);
2435         $result2 = delete_user($usersharedemail2);
2437         $usersharedemail1after = $DB->get_record('user', array('id' => $usersharedemail1->id));
2438         $usersharedemail2after = $DB->get_record('user', array('id' => $usersharedemail2->id));
2439         $this->assertTrue($result1);
2440         $this->assertTrue($result2);
2441         $this->assertStringStartsWith($usersharedemail1->email . '.', $usersharedemail1after->username);
2442         $this->assertStringStartsWith($usersharedemail2->email . '.', $usersharedemail2after->username);
2444         // Simultaneously deleting users without email addresses.
2445         $result1 = delete_user($useremptyemail1);
2446         $result2 = delete_user($useremptyemail2);
2448         $useremptyemail1after = $DB->get_record('user', array('id' => $useremptyemail1->id));
2449         $useremptyemail2after = $DB->get_record('user', array('id' => $useremptyemail2->id));
2450         $this->assertTrue($result1);
2451         $this->assertTrue($result2);
2452         $this->assertStringStartsWith($useremptyemail1->username . '.' . $useremptyemail1->id . '@unknownemail.invalid.',
2453             $useremptyemail1after->username);
2454         $this->assertStringStartsWith($useremptyemail2->username . '.' . $useremptyemail2->id . '@unknownemail.invalid.',
2455             $useremptyemail2after->username);
2457         $this->resetDebugging();
2458     }
2460     /**
2461      * Test function convert_to_array()
2462      */
2463     public function test_convert_to_array() {
2464         // Check that normal classes are converted to arrays the same way as (array) would do.
2465         $obj = new stdClass();
2466         $obj->prop1 = 'hello';
2467         $obj->prop2 = array('first', 'second', 13);
2468         $obj->prop3 = 15;
2469         $this->assertEquals(convert_to_array($obj), (array)$obj);
2471         // Check that context object (with iterator) is converted to array properly.
2472         $obj = context_system::instance();
2473         $ar = array(
2474             'id'           => $obj->id,
2475             'contextlevel' => $obj->contextlevel,
2476             'instanceid'   => $obj->instanceid,
2477             'path'         => $obj->path,
2478             'depth'        => $obj->depth,
2479             'locked'       => $obj->locked,
2480         );
2481         $this->assertEquals(convert_to_array($obj), $ar);
2482     }
2484     /**
2485      * Test the function date_format_string().
2486      */
2487     public function test_date_format_string() {
2488         global $CFG;
2490         $this->resetAfterTest();
2491         $this->setTimezone(99, 'Australia/Perth');
2493         $tests = array(
2494             array(
2495                 'tz' => 99,
2496                 'str' => '%A, %d %B %Y, %I:%M %p',
2497                 'expected' => 'Saturday, 01 January 2011, 06:00 PM'
2498             ),
2499             array(
2500                 'tz' => 0,
2501                 'str' => '%A, %d %B %Y, %I:%M %p',
2502                 'expected' => 'Saturday, 01 January 2011, 10:00 AM'
2503             ),
2504             array(
2505                 // Note: this function expected the timestamp in weird format before,
2506                 // since 2.9 it uses UTC.
2507                 'tz' => 'Pacific/Auckland',
2508                 'str' => '%A, %d %B %Y, %I:%M %p',
2509                 'expected' => 'Saturday, 01 January 2011, 11:00 PM'
2510             ),
2511             // Following tests pass on Windows only because en lang pack does
2512             // not contain localewincharset, in real life lang pack maintainers
2513             // may use only characters that are present in localewincharset
2514             // in format strings!
2515             array(
2516                 'tz' => 99,
2517                 'str' => 'Žluťoučký koníček %A',
2518                 'expected' => 'Žluťoučký koníček Saturday'
2519             ),
2520             array(
2521                 'tz' => 99,
2522                 'str' => '言語設定言語 %A',
2523                 'expected' => '言語設定言語 Saturday'
2524             ),
2525             array(
2526                 'tz' => 99,
2527                 'str' => '简体中文简体 %A',
2528                 'expected' => '简体中文简体 Saturday'
2529             ),
2530         );
2532         // Note: date_format_string() uses the timezone only to differenciate
2533         // the server time from the UTC time. It does not modify the timestamp.
2534         // Hence similar results for timezones <= 13.
2535         // On different systems case of AM PM changes so compare case insensitive.
2536         foreach ($tests as $test) {
2537             $str = date_format_string(1293876000, $test['str'], $test['tz']);
2538             $this->assertSame(core_text::strtolower($test['expected']), core_text::strtolower($str));
2539         }
2540     }
2542     public function test_get_config() {
2543         global $CFG;
2545         $this->resetAfterTest();
2547         // Preparation.
2548         set_config('phpunit_test_get_config_1', 'test 1');
2549         set_config('phpunit_test_get_config_2', 'test 2', 'mod_forum');
2550         if (!is_array($CFG->config_php_settings)) {
2551             $CFG->config_php_settings = array();
2552         }
2553         $CFG->config_php_settings['phpunit_test_get_config_3'] = 'test 3';
2555         if (!is_array($CFG->forced_plugin_settings)) {
2556             $CFG->forced_plugin_settings = array();
2557         }
2558         if (!array_key_exists('mod_forum', $CFG->forced_plugin_settings)) {
2559             $CFG->forced_plugin_settings['mod_forum'] = array();
2560         }
2561         $CFG->forced_plugin_settings['mod_forum']['phpunit_test_get_config_4'] = 'test 4';
2562         $CFG->phpunit_test_get_config_5 = 'test 5';
2564         // Testing.
2565         $this->assertSame('test 1', get_config('core', 'phpunit_test_get_config_1'));
2566         $this->assertSame('test 2', get_config('mod_forum', 'phpunit_test_get_config_2'));
2567         $this->assertSame('test 3', get_config('core', 'phpunit_test_get_config_3'));
2568         $this->assertSame('test 4', get_config('mod_forum', 'phpunit_test_get_config_4'));
2569         $this->assertFalse(get_config('core', 'phpunit_test_get_config_5'));
2570         $this->assertFalse(get_config('core', 'phpunit_test_get_config_x'));
2571         $this->assertFalse(get_config('mod_forum', 'phpunit_test_get_config_x'));
2573         // Test config we know to exist.
2574         $this->assertSame($CFG->dataroot, get_config('core', 'dataroot'));
2575         $this->assertSame($CFG->phpunit_dataroot, get_config('core', 'phpunit_dataroot'));
2576         $this->assertSame($CFG->dataroot, get_config('core', 'phpunit_dataroot'));
2577         $this->assertSame(get_config('core', 'dataroot'), get_config('core', 'phpunit_dataroot'));
2579         // Test setting a config var that already exists.
2580         set_config('phpunit_test_get_config_1', 'test a');
2581         $this->assertSame('test a', $CFG->phpunit_test_get_config_1);
2582         $this->assertSame('test a', get_config('core', 'phpunit_test_get_config_1'));
2584         // Test cache invalidation.
2585         $cache = cache::make('core', 'config');
2586         $this->assertInternalType('array', $cache->get('core'));
2587         $this->assertInternalType('array', $cache->get('mod_forum'));
2588         set_config('phpunit_test_get_config_1', 'test b');
2589         $this->assertFalse($cache->get('core'));
2590         set_config('phpunit_test_get_config_4', 'test c', 'mod_forum');
2591         $this->assertFalse($cache->get('mod_forum'));
2592     }
2594     public function test_get_max_upload_sizes() {
2595         // Test with very low limits so we are not affected by php upload limits.
2596         // Test activity limit smallest.
2597         $sitebytes = 102400;
2598         $coursebytes = 51200;
2599         $modulebytes = 10240;
2600         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2602         $this->assertSame('Activity upload limit (10KB)', $result['0']);
2603         $this->assertCount(2, $result);
2605         // Test course limit smallest.
2606         $sitebytes = 102400;
2607         $coursebytes = 10240;
2608         $modulebytes = 51200;
2609         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2611         $this->assertSame('Course upload limit (10KB)', $result['0']);
2612         $this->assertCount(2, $result);
2614         // Test site limit smallest.
2615         $sitebytes = 10240;
2616         $coursebytes = 102400;
2617         $modulebytes = 51200;
2618         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2620         $this->assertSame('Site upload limit (10KB)', $result['0']);
2621         $this->assertCount(2, $result);
2623         // Test site limit not set.
2624         $sitebytes = 0;
2625         $coursebytes = 102400;
2626         $modulebytes = 51200;
2627         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2629         $this->assertSame('Activity upload limit (50KB)', $result['0']);
2630         $this->assertCount(3, $result);
2632         $sitebytes = 0;
2633         $coursebytes = 51200;
2634         $modulebytes = 102400;
2635         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes);
2637         $this->assertSame('Course upload limit (50KB)', $result['0']);
2638         $this->assertCount(3, $result);
2640         // Test custom bytes in range.
2641         $sitebytes = 102400;
2642         $coursebytes = 51200;
2643         $modulebytes = 51200;
2644         $custombytes = 10240;
2645         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2647         $this->assertCount(3, $result);
2649         // Test custom bytes in range but non-standard.
2650         $sitebytes = 102400;
2651         $coursebytes = 51200;
2652         $modulebytes = 51200;
2653         $custombytes = 25600;
2654         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2656         $this->assertCount(4, $result);
2658         // Test custom bytes out of range.
2659         $sitebytes = 102400;
2660         $coursebytes = 51200;
2661         $modulebytes = 51200;
2662         $custombytes = 102400;
2663         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2665         $this->assertCount(3, $result);
2667         // Test custom bytes out of range and non-standard.
2668         $sitebytes = 102400;
2669         $coursebytes = 51200;
2670         $modulebytes = 51200;
2671         $custombytes = 256000;
2672         $result = get_max_upload_sizes($sitebytes, $coursebytes, $modulebytes, $custombytes);
2674         $this->assertCount(3, $result);
2676         // Test site limit only.
2677         $sitebytes = 51200;
2678         $result = get_max_upload_sizes($sitebytes);
2680         $this->assertSame('Site upload limit (50KB)', $result['0']);
2681         $this->assertSame('50KB', $result['51200']);
2682         $this->assertSame('10KB', $result['10240']);
2683         $this->assertCount(3, $result);
2685         // Test no limit.
2686         $result = get_max_upload_sizes();
2687         $this->assertArrayHasKey('0', $result);
2688         $this->assertArrayHasKey(get_max_upload_file_size(), $result);
2689     }
2691     /**
2692      * Test function password_is_legacy_hash().
2693      */
2694     public function test_password_is_legacy_hash() {
2695         // Well formed md5s should be matched.
2696         foreach (array('some', 'strings', 'to_check!') as $string) {
2697             $md5 = md5($string);
2698             $this->assertTrue(password_is_legacy_hash($md5));
2699         }
2700         // Strings that are not md5s should not be matched.
2701         foreach (array('', AUTH_PASSWORD_NOT_CACHED, 'IPW8WTcsWNgAWcUS1FBVHegzJnw5M2jOmYkmfc8z.xdBOyC4Caeum') as $notmd5) {
2702             $this->assertFalse(password_is_legacy_hash($notmd5));
2703         }
2704     }
2706     /**
2707      * Test function validate_internal_user_password().
2708      */
2709     public function test_validate_internal_user_password() {
2710         // Test bcrypt hashes.
2711         $validhashes = array(
2712             'pw' => '$2y$10$LOSDi5eaQJhutSRun.OVJ.ZSxQZabCMay7TO1KmzMkDMPvU40zGXK',
2713             'abc' => '$2y$10$VWTOhVdsBbWwtdWNDRHSpewjd3aXBQlBQf5rBY/hVhw8hciarFhXa',
2714             'C0mP1eX_&}<?@*&%` |\"' => '$2y$10$3PJf.q.9ywNJlsInPbqc8.IFeSsvXrGvQLKRFBIhVu1h1I3vpIry6',
2715             'ĩńťėŕňăţĩōŋāĹ' => '$2y$10$3A2Y8WpfRAnP3czJiSv6N.6Xp0T8hW3QZz2hUCYhzyWr1kGP1yUve'
2716         );
2718         foreach ($validhashes as $password => $hash) {
2719             $user = new stdClass();
2720             $user->auth = 'manual';
2721             $user->password = $hash;
2722             // The correct password should be validated.
2723             $this->assertTrue(validate_internal_user_password($user, $password));
2724             // An incorrect password should not be validated.
2725             $this->assertFalse(validate_internal_user_password($user, 'badpw'));
2726         }
2727     }
2729     /**
2730      * Test function hash_internal_user_password().
2731      */
2732     public function test_hash_internal_user_password() {
2733         $passwords = array('pw', 'abc123', 'C0mP1eX_&}<?@*&%` |\"', 'ĩńťėŕňăţĩōŋāĹ');
2735         // Check that some passwords that we convert to hashes can
2736         // be validated.
2737         foreach ($passwords as $password) {
2738             $hash = hash_internal_user_password($password);
2739             $fasthash = hash_internal_user_password($password, true);
2740             $user = new stdClass();
2741             $user->auth = 'manual';
2742             $user->password = $hash;
2743             $this->assertTrue(validate_internal_user_password($user, $password));
2745             // They should not be in md5 format.
2746             $this->assertFalse(password_is_legacy_hash($hash));
2748             // Check that cost factor in hash is correctly set.
2749             $this->assertRegExp('/\$10\$/', $hash);
2750             $this->assertRegExp('/\$04\$/', $fasthash);
2751         }
2752     }
2754     /**
2755      * Test function update_internal_user_password().
2756      */
2757     public function test_update_internal_user_password() {
2758         global $DB;
2759         $this->resetAfterTest();
2760         $passwords = array('password', '1234', 'changeme', '****');
2761         foreach ($passwords as $password) {
2762             $user = $this->getDataGenerator()->create_user(array('auth'=>'manual'));
2763             update_internal_user_password($user, $password);
2764             // The user object should have been updated.
2765             $this->assertTrue(validate_internal_user_password($user, $password));
2766             // The database field for the user should also have been updated to the
2767             // same value.
2768             $this->assertSame($user->password, $DB->get_field('user', 'password', array('id' => $user->id)));
2769         }
2771         $user = $this->getDataGenerator()->create_user(array('auth'=>'manual'));
2772         // Manually set the user's password to the md5 of the string 'password'.
2773         $DB->set_field('user', 'password', '5f4dcc3b5aa765d61d8327deb882cf99', array('id' => $user->id));
2775         $sink = $this->redirectEvents();
2776         // Update the password.
2777         update_internal_user_password($user, 'password');
2778         $events = $sink->get_events();
2779         $sink->close();
2780         $event = array_pop($events);
2782         // Password should have been updated to a bcrypt hash.
2783         $this->assertFalse(password_is_legacy_hash($user->password));
2785         // Verify event information.
2786         $this->assertInstanceOf('\core\event\user_password_updated', $event);
2787         $this->assertSame($user->id, $event->relateduserid);
2788         $this->assertEquals(context_user::instance($user->id), $event->get_context());
2789         $this->assertEventContextNotUsed($event);
2791         // Verify recovery of property 'auth'.
2792         unset($user->auth);
2793         update_internal_user_password($user, 'newpassword');
2794         $this->assertDebuggingCalled('User record in update_internal_user_password() must include field auth',
2795                 DEBUG_DEVELOPER);
2796         $this->assertEquals('manual', $user->auth);
2797     }
2799     /**
2800      * Testing that if the password is not cached, that it does not update
2801      * the user table and fire event.
2802      */
2803     public function test_update_internal_user_password_no_cache() {
2804         $this->resetAfterTest();
2806         $user = $this->getDataGenerator()->create_user(array('auth' => 'cas'));
2807         $this->assertEquals(AUTH_PASSWORD_NOT_CACHED, $user->password);
2809         $sink = $this->redirectEvents();
2810         update_internal_user_password($user, 'wonkawonka');
2811         $this->assertEquals(0, $sink->count(), 'User updated event should not fire');
2812     }
2814     /**
2815      * Test if the user has a password hash, but now their auth method
2816      * says not to cache it.  Then it should update.
2817      */
2818     public function test_update_internal_user_password_update_no_cache() {
2819         $this->resetAfterTest();
2821         $user = $this->getDataGenerator()->create_user(array('password' => 'test'));
2822         $this->assertNotEquals(AUTH_PASSWORD_NOT_CACHED, $user->password);
2823         $user->auth = 'cas'; // Change to a auth that does not store passwords.
2825         $sink = $this->redirectEvents();
2826         update_internal_user_password($user, 'wonkawonka');
2827         $this->assertGreaterThanOrEqual(1, $sink->count(), 'User updated event should fire');
2829         $this->assertEquals(AUTH_PASSWORD_NOT_CACHED, $user->password);
2830     }
2832     public function test_fullname() {
2833         global $CFG;
2835         $this->resetAfterTest();
2837         // Create a user to test the name display on.
2838         $record = array();
2839         $record['firstname'] = 'Scott';
2840         $record['lastname'] = 'Fletcher';
2841         $record['firstnamephonetic'] = 'スコット';
2842         $record['lastnamephonetic'] = 'フレチャー';
2843         $record['alternatename'] = 'No friends';
2844         $user = $this->getDataGenerator()->create_user($record);
2846         // Back up config settings for restore later.
2847         $originalcfg = new stdClass();
2848         $originalcfg->fullnamedisplay = $CFG->fullnamedisplay;
2849         $originalcfg->alternativefullnameformat = $CFG->alternativefullnameformat;
2851         // Testing existing fullnamedisplay settings.
2852         $CFG->fullnamedisplay = 'firstname';
2853         $testname = fullname($user);
2854         $this->assertSame($user->firstname, $testname);
2856         $CFG->fullnamedisplay = 'firstname lastname';
2857         $expectedname = "$user->firstname $user->lastname";
2858         $testname = fullname($user);
2859         $this->assertSame($expectedname, $testname);
2861         $CFG->fullnamedisplay = 'lastname firstname';
2862         $expectedname = "$user->lastname $user->firstname";
2863         $testname = fullname($user);
2864         $this->assertSame($expectedname, $testname);
2866         $expectedname = get_string('fullnamedisplay', null, $user);
2867         $CFG->fullnamedisplay = 'language';
2868         $testname = fullname($user);
2869         $this->assertSame($expectedname, $testname);
2871         // Test override parameter.
2872         $CFG->fullnamedisplay = 'firstname';
2873         $expectedname = "$user->firstname $user->lastname";
2874         $testname = fullname($user, true);
2875         $this->assertSame($expectedname, $testname);
2877         // Test alternativefullnameformat setting.
2878         // Test alternativefullnameformat that has been set to nothing.
2879         $CFG->alternativefullnameformat = '';
2880         $expectedname = "$user->firstname $user->lastname";
2881         $testname = fullname($user, true);
2882         $this->assertSame($expectedname, $testname);
2884         // Test alternativefullnameformat that has been set to 'language'.
2885         $CFG->alternativefullnameformat = 'language';
2886         $expectedname = "$user->firstname $user->lastname";
2887         $testname = fullname($user, true);
2888         $this->assertSame($expectedname, $testname);
2890         // Test customising the alternativefullnameformat setting with all additional name fields.
2891         $CFG->alternativefullnameformat = 'firstname lastname firstnamephonetic lastnamephonetic middlename alternatename';
2892         $expectedname = "$user->firstname $user->lastname $user->firstnamephonetic $user->lastnamephonetic $user->middlename $user->alternatename";
2893         $testname = fullname($user, true);
2894         $this->assertSame($expectedname, $testname);
2896         // Test additional name fields.
2897         $CFG->fullnamedisplay = 'lastname lastnamephonetic firstname firstnamephonetic';
2898         $expectedname = "$user->lastname $user->lastnamephonetic $user->firstname $user->firstnamephonetic";
2899         $testname = fullname($user);
2900         $this->assertSame($expectedname, $testname);
2902         // Test for handling missing data.
2903         $user->middlename = null;
2904         // Parenthesis with no data.
2905         $CFG->fullnamedisplay = 'firstname (middlename) lastname';
2906         $expectedname = "$user->firstname $user->lastname";
2907         $testname = fullname($user);
2908         $this->assertSame($expectedname, $testname);
2910         // Extra spaces due to no data.
2911         $CFG->fullnamedisplay = 'firstname middlename lastname';
2912         $expectedname = "$user->firstname $user->lastname";
2913         $testname = fullname($user);
2914         $this->assertSame($expectedname, $testname);
2916         // Regular expression testing.
2917         // Remove some data from the user fields.
2918         $user->firstnamephonetic = '';
2919         $user->lastnamephonetic = '';
2921         // Removing empty brackets and excess whitespace.
2922         // All of these configurations should resolve to just firstname lastname.
2923         $configarray = array();
2924         $configarray[] = 'firstname lastname [firstnamephonetic lastnamephonetic]';
2925         $configarray[] = 'firstname lastname \'middlename\'';
2926         $configarray[] = 'firstname "firstnamephonetic" lastname';
2927         $configarray[] = 'firstname 「firstnamephonetic」 lastname 「lastnamephonetic」';
2929         foreach ($configarray as $config) {
2930             $CFG->fullnamedisplay = $config;
2931             $expectedname = "$user->firstname $user->lastname";
2932             $testname = fullname($user);
2933             $this->assertSame($expectedname, $testname);
2934         }
2936         // Check to make sure that other characters are left in place.
2937         $configarray = array();
2938         $configarray['0'] = new stdClass();
2939         $configarray['0']->config = 'lastname firstname, middlename';
2940         $configarray['0']->expectedname = "$user->lastname $user->firstname,";
2941         $configarray['1'] = new stdClass();
2942         $configarray['1']->config = 'lastname firstname + alternatename';
2943         $configarray['1']->expectedname = "$user->lastname $user->firstname + $user->alternatename";
2944         $configarray['2'] = new stdClass();
2945         $configarray['2']->config = 'firstname aka: alternatename';
2946         $configarray['2']->expectedname = "$user->firstname aka: $user->alternatename";
2947         $configarray['3'] = new stdClass();
2948         $configarray['3']->config = 'firstname (alternatename)';
2949         $configarray['3']->expectedname = "$user->firstname ($user->alternatename)";
2950         $configarray['4'] = new stdClass();
2951         $configarray['4']->config = 'firstname [alternatename]';
2952         $configarray['4']->expectedname = "$user->firstname [$user->alternatename]";
2953         $configarray['5'] = new stdClass();
2954         $configarray['5']->config = 'firstname "lastname"';
2955         $configarray['5']->expectedname = "$user->firstname \"$user->lastname\"";
2957         foreach ($configarray as $config) {
2958             $CFG->fullnamedisplay = $config->config;
2959             $expectedname = $config->expectedname;
2960             $testname = fullname($user);
2961             $this->assertSame($expectedname, $testname);
2962         }
2964         // Test debugging message displays when
2965         // fullnamedisplay setting is "normal".
2966         $CFG->fullnamedisplay = 'firstname lastname';
2967         unset($user);
2968         $user = new stdClass();
2969         $user->firstname = 'Stan';
2970         $user->lastname = 'Lee';
2971         $namedisplay = fullname($user);
2972         $this->assertDebuggingCalled();
2974         // Tidy up after we finish testing.
2975         $CFG->fullnamedisplay = $originalcfg->fullnamedisplay;
2976         $CFG->alternativefullnameformat = $originalcfg->alternativefullnameformat;
2977     }
2979     public function test_get_all_user_name_fields() {
2980         $this->resetAfterTest();
2982         // Additional names in an array.
2983         $testarray = array('firstnamephonetic' => 'firstnamephonetic',
2984                 'lastnamephonetic' => 'lastnamephonetic',
2985                 'middlename' => 'middlename',
2986                 'alternatename' => 'alternatename',
2987                 'firstname' => 'firstname',
2988                 'lastname' => 'lastname');
2989         $this->assertEquals($testarray, get_all_user_name_fields());
2991         // Additional names as a string.
2992         $teststring = 'firstnamephonetic,lastnamephonetic,middlename,alternatename,firstname,lastname';
2993         $this->assertEquals($teststring, get_all_user_name_fields(true));
2995         // Additional names as a string with an alias.
2996         $teststring = 't.firstnamephonetic,t.lastnamephonetic,t.middlename,t.alternatename,t.firstname,t.lastname';
2997         $this->assertEquals($teststring, get_all_user_name_fields(true, 't'));
2999         // Additional name fields with a prefix - object.
3000         $testarray = array('firstnamephonetic' => 'authorfirstnamephonetic',
3001                 'lastnamephonetic' => 'authorlastnamephonetic',
3002                 'middlename' => 'authormiddlename',
3003                 'alternatename' => 'authoralternatename',
3004                 'firstname' => 'authorfirstname',
3005                 'lastname' => 'authorlastname');
3006         $this->assertEquals($testarray, get_all_user_name_fields(false, null, 'author'));
3008         // Additional name fields with an alias and a title - string.
3009         $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';
3010         $this->assertEquals($teststring, get_all_user_name_fields(true, 'u', null, 'author'));
3012         // Test the order parameter of the function.
3013         // Returning an array.
3014         $testarray = array('firstname' => 'firstname',
3015                 'lastname' => 'lastname',
3016                 'firstnamephonetic' => 'firstnamephonetic',
3017                 'lastnamephonetic' => 'lastnamephonetic',
3018                 'middlename' => 'middlename',
3019                 'alternatename' => 'alternatename'
3020         );
3021         $this->assertEquals($testarray, get_all_user_name_fields(false, null, null, null, true));
3023         // Returning a string.
3024         $teststring = 'firstname,lastname,firstnamephonetic,lastnamephonetic,middlename,alternatename';
3025         $this->assertEquals($teststring, get_all_user_name_fields(true, null, null, null, true));
3026     }
3028     public function test_order_in_string() {
3029         $this->resetAfterTest();
3031         // Return an array in an order as they are encountered in a string.
3032         $valuearray = array('second', 'firsthalf', 'first');
3033         $formatstring = 'first firsthalf some other text (second)';
3034         $expectedarray = array('0' => 'first', '6' => 'firsthalf', '33' => 'second');
3035         $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3037         // Try again with a different order for the format.
3038         $valuearray = array('second', 'firsthalf', 'first');
3039         $formatstring = 'firsthalf first second';
3040         $expectedarray = array('0' => 'firsthalf', '10' => 'first', '16' => 'second');
3041         $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3043         // Try again with yet another different order for the format.
3044         $valuearray = array('second', 'firsthalf', 'first');
3045         $formatstring = 'start seconds away second firstquater first firsthalf';
3046         $expectedarray = array('19' => 'second', '38' => 'first', '44' => 'firsthalf');
3047         $this->assertEquals($expectedarray, order_in_string($valuearray, $formatstring));
3048     }
3050     public function test_complete_user_login() {
3051         global $USER, $DB;
3053         $this->resetAfterTest();
3054         $user = $this->getDataGenerator()->create_user();
3055         $this->setUser(0);
3057         $sink = $this->redirectEvents();
3058         $loginuser = clone($user);
3059         $this->setCurrentTimeStart();
3060         @complete_user_login($loginuser); // Hide session header errors.
3061         $this->assertSame($loginuser, $USER);
3062         $this->assertEquals($user->id, $USER->id);
3063         $events = $sink->get_events();
3064         $sink->close();
3066         $this->assertCount(1, $events);
3067         $event = reset($events);
3068         $this->assertInstanceOf('\core\event\user_loggedin', $event);
3069         $this->assertEquals('user', $event->objecttable);
3070         $this->assertEquals($user->id, $event->objectid);
3071         $this->assertEquals(context_system::instance()->id, $event->contextid);
3072         $this->assertEventContextNotUsed($event);
3074         $user = $DB->get_record('user', array('id'=>$user->id));
3076         $this->assertTimeCurrent($user->firstaccess);
3077         $this->assertTimeCurrent($user->lastaccess);
3079         $this->assertTimeCurrent($USER->firstaccess);
3080         $this->assertTimeCurrent($USER->lastaccess);
3081         $this->assertTimeCurrent($USER->currentlogin);
3082         $this->assertSame(sesskey(), $USER->sesskey);
3083         $this->assertTimeCurrent($USER->preference['_lastloaded']);
3084         $this->assertObjectNotHasAttribute('password', $USER);
3085         $this->assertObjectNotHasAttribute('description', $USER);
3086     }
3088     /**
3089      * Test require_logout.
3090      */
3091     public function test_require_logout() {
3092         $this->resetAfterTest();
3093         $user = $this->getDataGenerator()->create_user();
3094         $this->setUser($user);
3096         $this->assertTrue(isloggedin());
3098         // Logout user and capture event.
3099         $sink = $this->redirectEvents();
3100         require_logout();
3101         $events = $sink->get_events();
3102         $sink->close();
3103         $event = array_pop($events);
3105         // Check if user is logged out.
3106         $this->assertFalse(isloggedin());
3108         // Test Event.
3109         $this->assertInstanceOf('\core\event\user_loggedout', $event);
3110         $this->assertSame($user->id, $event->objectid);
3111         $this->assertSame('user_logout', $event->get_legacy_eventname());
3112         $this->assertEventLegacyData($user, $event);
3113         $expectedlogdata = array(SITEID, 'user', 'logout', 'view.php?id='.$event->objectid.'&course='.SITEID, $event->objectid, 0,
3114             $event->objectid);
3115         $this->assertEventLegacyLogData($expectedlogdata, $event);
3116         $this->assertEventContextNotUsed($event);
3117     }
3119     /**
3120      * A data provider for testing email messageid
3121      */
3122     public function generate_email_messageid_provider() {
3123         return array(
3124             'nopath' => array(
3125                 'wwwroot' => 'http://www.example.com',
3126                 'ids' => array(
3127                     'a-custom-id' => '<a-custom-id@www.example.com>',
3128                     'an-id-with-/-a-slash' => '<an-id-with-%2F-a-slash@www.example.com>',
3129                 ),
3130             ),
3131             'path' => array(
3132                 'wwwroot' => 'http://www.example.com/path/subdir',
3133                 'ids' => array(
3134                     'a-custom-id' => '<a-custom-id/path/subdir@www.example.com>',
3135                     'an-id-with-/-a-slash' => '<an-id-with-%2F-a-slash/path/subdir@www.example.com>',
3136                 ),
3137             ),
3138         );
3139     }
3141     /**
3142      * Test email message id generation
3143      *
3144      * @dataProvider generate_email_messageid_provider
3145      *
3146      * @param string $wwwroot The wwwroot
3147      * @param array $msgids An array of msgid local parts and the final result
3148      */
3149     public function test_generate_email_messageid($wwwroot, $msgids) {
3150         global $CFG;
3152         $this->resetAfterTest();
3153         $CFG->wwwroot = $wwwroot;
3155         foreach ($msgids as $local => $final) {
3156             $this->assertEquals($final, generate_email_messageid($local));
3157         }
3158     }
3160     /**
3161      * A data provider for testing email diversion
3162      */
3163     public function diverted_emails_provider() {
3164         return array(
3165             'nodiverts' => array(
3166                 'divertallemailsto' => null,
3167                 'divertallemailsexcept' => null,
3168                 array(
3169                     'foo@example.com',
3170                     'test@real.com',
3171                     'fred.jones@example.com',
3172                     'dev1@dev.com',
3173                     'fred@example.com',
3174                     'fred+verp@example.com',
3175                 ),
3176                 false,
3177             ),
3178             'alldiverts' => array(
3179                 'divertallemailsto' => 'somewhere@elsewhere.com',
3180                 'divertallemailsexcept' => null,
3181                 array(
3182                     'foo@example.com',
3183                     'test@real.com',
3184                     'fred.jones@example.com',
3185                     'dev1@dev.com',
3186                     'fred@example.com',
3187                     'fred+verp@example.com',
3188                 ),
3189                 true,
3190             ),
3191             'alsodiverts' => array(
3192                 'divertallemailsto' => 'somewhere@elsewhere.com',
3193                 'divertallemailsexcept' => '@dev.com, fred(\+.*)?@example.com',
3194                 array(
3195                     'foo@example.com',
3196                     'test@real.com',
3197                     'fred.jones@example.com',
3198                 ),
3199                 true,
3200             ),
3201             'divertsexceptions' => array(
3202                 'divertallemailsto' => 'somewhere@elsewhere.com',
3203                 'divertallemailsexcept' => '@dev.com, fred(\+.*)?@example.com',
3204                 array(
3205                     'dev1@dev.com',
3206                     'fred@example.com',
3207                     'fred+verp@example.com',
3208                 ),
3209                 false,
3210             ),
3211         );
3212     }
3214     /**
3215      * Test email diversion
3216      *
3217      * @dataProvider diverted_emails_provider
3218      *
3219      * @param string $divertallemailsto An optional email address
3220      * @param string $divertallemailsexcept An optional exclusion list
3221      * @param array $addresses An array of test addresses
3222      * @param boolean $expected Expected result
3223      */
3224     public function test_email_should_be_diverted($divertallemailsto, $divertallemailsexcept, $addresses, $expected) {
3225         global $CFG;
3227         $this->resetAfterTest();
3228         $CFG->divertallemailsto = $divertallemailsto;
3229         $CFG->divertallemailsexcept = $divertallemailsexcept;
3231         foreach ($addresses as $address) {
3232             $this->assertEquals($expected, email_should_be_diverted($address));
3233         }
3234     }
3236     public function test_email_to_user() {
3237         global $CFG;
3239         $this->resetAfterTest();
3241         $user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1, 'mailformat' => 0));
3242         $user2 = $this->getDataGenerator()->create_user(array('maildisplay' => 1, 'mailformat' => 1));
3243         $user3 = $this->getDataGenerator()->create_user(array('maildisplay' => 0));
3244         set_config('allowedemaildomains', "example.com\r\nmoodle.org");
3246         $subject = 'subject';
3247         $messagetext = 'message text';
3248         $subject2 = 'subject 2';
3249         $messagetext2 = '<b>message text 2</b>';
3251         // Close the default email sink.
3252         $sink = $this->redirectEmails();
3253         $sink->close();
3255         $CFG->noemailever = true;
3256         $this->assertNotEmpty($CFG->noemailever);
3257         email_to_user($user1, $user2, $subject, $messagetext);
3258         $this->assertDebuggingCalled('Not sending email due to $CFG->noemailever config setting');
3260         unset_config('noemailever');
3262         email_to_user($user1, $user2, $subject, $messagetext);
3263         $this->assertDebuggingCalled('Unit tests must not send real emails! Use $this->redirectEmails()');
3265         $sink = $this->redirectEmails();
3266         email_to_user($user1, $user2, $subject, $messagetext);
3267         email_to_user($user2, $user1, $subject2, $messagetext2);
3268         $this->assertSame(2, $sink->count());
3269         $result = $sink->get_messages();
3270         $this->assertCount(2, $result);
3271         $sink->close();
3273         $this->assertSame($subject, $result[0]->subject);
3274         $this->assertSame($messagetext, trim($result[0]->body));
3275         $this->assertSame($user1->email, $result[0]->to);
3276         $this->assertSame($user2->email, $result[0]->from);
3277         $this->assertContains('Content-Type: text/plain', $result[0]->header);
3279         $this->assertSame($subject2, $result[1]->subject);
3280         $this->assertContains($messagetext2, quoted_printable_decode($result[1]->body));
3281         $this->assertSame($user2->email, $result[1]->to);
3282         $this->assertSame($user1->email, $result[1]->from);
3283         $this->assertNotContains('Content-Type: text/plain', $result[1]->header);
3285         email_to_user($user1, $user2, $subject, $messagetext);
3286         $this->assertDebuggingCalled('Unit tests must not send real emails! Use $this->redirectEmails()');
3288         // Test that an empty noreplyaddress will default to a no-reply address.
3289         $sink = $this->redirectEmails();
3290         email_to_user($user1, $user3, $subject, $messagetext);
3291         $result = $sink->get_messages();
3292         $this->assertEquals($CFG->noreplyaddress, $result[0]->from);
3293         $sink->close();
3294         set_config('noreplyaddress', '');
3295         $sink = $this->redirectEmails();
3296         email_to_user($user1, $user3, $subject, $messagetext);
3297         $result = $sink->get_messages();
3298         $this->assertEquals('noreply@www.example.com', $result[0]->from);
3299         $sink->close();
3301         // Test $CFG->allowedemaildomains.
3302         set_config('noreplyaddress', 'noreply@www.example.com');
3303         $this->assertNotEmpty($CFG->allowedemaildomains);
3304         $sink = $this->redirectEmails();
3305         email_to_user($user1, $user2, $subject, $messagetext);
3306         unset_config('allowedemaildomains');
3307         email_to_user($user1, $user2, $subject, $messagetext);
3308         $result = $sink->get_messages();
3309         $this->assertNotEquals($CFG->noreplyaddress, $result[0]->from);
3310         $this->assertEquals($CFG->noreplyaddress, $result[1]->from);
3311         $sink->close();
3313         // Try to send an unsafe attachment, we should see an error message in the eventual mail body.
3314         $attachment = '../test.txt';
3315         $attachname = 'txt';
3317         $sink = $this->redirectEmails();
3318         email_to_user($user1, $user2, $subject, $messagetext, '', $attachment, $attachname);
3319         $this->assertSame(1, $sink->count());
3320         $result = $sink->get_messages();
3321         $this->assertCount(1, $result);
3322         $this->assertContains('error.txt', $result[0]->body);
3323         $this->assertContains('Error in attachment.  User attempted to attach a filename with a unsafe name.', $result[0]->body);
3324         $sink->close();
3325     }
3327     /**
3328      * Test setnew_password_and_mail.
3329      */
3330     public function test_setnew_password_and_mail() {
3331         global $DB, $CFG;
3333         $this->resetAfterTest();
3335         $user = $this->getDataGenerator()->create_user();
3337         // Update user password.
3338         $sink = $this->redirectEvents();
3339         $sink2 = $this->redirectEmails(); // Make sure we are redirecting emails.
3340         setnew_password_and_mail($user);
3341         $events = $sink->get_events();
3342         $sink->close();
3343         $sink2->close();
3344         $event = array_pop($events);
3346         // Test updated value.
3347         $dbuser = $DB->get_record('user', array('id' => $user->id));
3348         $this->assertSame($user->firstname, $dbuser->firstname);
3349         $this->assertNotEmpty($dbuser->password);
3351         // Test event.
3352         $this->assertInstanceOf('\core\event\user_password_updated', $event);
3353         $this->assertSame($user->id, $event->relateduserid);
3354         $this->assertEquals(context_user::instance($user->id), $event->get_context());
3355         $this->assertEventContextNotUsed($event);
3356     }
3358     /**
3359      * Data provider for test_generate_confirmation_link
3360      * @return Array of confirmation urls and expected resultant confirmation links
3361      */
3362     public function generate_confirmation_link_provider() {
3363         global $CFG;
3364         return [
3365             "Simple name" => [
3366                 "username" => "simplename",
3367                 "confirmationurl" => null,
3368                 "expected" => $CFG->wwwroot . "/login/confirm.php?data=/simplename"
3369             ],
3370             "Period in between words in username" => [
3371                 "username" => "period.inbetween",
3372                 "confirmationurl" => null,
3373                 "expected" => $CFG->wwwroot . "/login/confirm.php?data=/period%2Einbetween"
3374             ],
3375             "Trailing periods in username" => [
3376                 "username" => "trailingperiods...",
3377                 "confirmationurl" => null,
3378                 "expected" => $CFG->wwwroot . "/login/confirm.php?data=/trailingperiods%2E%2E%2E"
3379             ],
3380             "At symbol in username" => [
3381                 "username" => "at@symbol",
3382                 "confirmationurl" => null,
3383                 "expected" => $CFG->wwwroot . "/login/confirm.php?data=/at%40symbol"
3384             ],
3385             "Dash symbol in username" => [
3386                 "username" => "has-dash",
3387                 "confirmationurl" => null,
3388                 "expected" => $CFG->wwwroot . "/login/confirm.php?data=/has-dash"
3389             ],
3390             "Underscore in username" => [
3391                 "username" => "under_score",
3392                 "confirmationurl" => null,
3393                 "expected" => $CFG->wwwroot . "/login/confirm.php?data=/under_score"
3394             ],
3395             "Many different characters in username" => [
3396                 "username" => "many_-.@characters@_@-..-..",
3397                 "confirmationurl" => null,
3398                 "expected" => $CFG->wwwroot . "/login/confirm.php?data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3399             ],
3400             "Custom relative confirmation url" => [
3401                 "username" => "many_-.@characters@_@-..-..",
3402                 "confirmationurl" => "/custom/local/url.php",
3403                 "expected" => $CFG->wwwroot . "/custom/local/url.php?data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3404             ],
3405             "Custom relative confirmation url with parameters" => [
3406                 "username" => "many_-.@characters@_@-..-..",
3407                 "confirmationurl" => "/custom/local/url.php?with=param",
3408                 "expected" => $CFG->wwwroot . "/custom/local/url.php?with=param&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3409             ],
3410             "Custom local confirmation url" => [
3411                 "username" => "many_-.@characters@_@-..-..",
3412                 "confirmationurl" => $CFG->wwwroot . "/custom/local/url.php",
3413                 "expected" => $CFG->wwwroot . "/custom/local/url.php?data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3414             ],
3415             "Custom local confirmation url with parameters" => [
3416                 "username" => "many_-.@characters@_@-..-..",
3417                 "confirmationurl" => $CFG->wwwroot . "/custom/local/url.php?with=param",
3418                 "expected" => $CFG->wwwroot . "/custom/local/url.php?with=param&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3419             ],
3420             "Custom external confirmation url" => [
3421                 "username" => "many_-.@characters@_@-..-..",
3422                 "confirmationurl" => "http://moodle.org/custom/external/url.php",
3423                 "expected" => "http://moodle.org/custom/external/url.php?data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3424             ],
3425             "Custom external confirmation url with parameters" => [
3426                 "username" => "many_-.@characters@_@-..-..",
3427                 "confirmationurl" => "http://moodle.org/ext.php?with=some&param=eters",
3428                 "expected" => "http://moodle.org/ext.php?with=some&param=eters&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3429             ],
3430             "Custom external confirmation url with parameters" => [
3431                 "username" => "many_-.@characters@_@-..-..",
3432                 "confirmationurl" => "http://moodle.org/ext.php?with=some&data=test",
3433                 "expected" => "http://moodle.org/ext.php?with=some&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E"
3434             ],
3435         ];
3436     }
3438     /**
3439      * Test generate_confirmation_link
3440      * @dataProvider generate_confirmation_link_provider
3441      * @param string $username The name of the user
3442      * @param string $confirmationurl The url the user should go to to confirm
3443      * @param string $expected The expected url of the confirmation link
3444      */
3445     public function test_generate_confirmation_link($username, $confirmationurl, $expected) {
3446         $this->resetAfterTest();
3447         $sink = $this->redirectEmails();
3449         $user = $this->getDataGenerator()->create_user(
3450             [
3451                 "username" => $username,
3452                 "confirmed" => false,
3453                 "email" => 'test@example.com',
3454             ]
3455         );
3457         send_confirmation_email($user, $confirmationurl);
3458         $sink->close();
3459         $messages = $sink->get_messages();
3460         $message = array_shift($messages);
3461         $messagebody = quoted_printable_decode($message->body);
3463         $this->assertContains($expected, $messagebody);
3464     }
3466     /**
3467      * Test generate_confirmation_link with custom admin link
3468      */
3469     public function test_generate_confirmation_link_with_custom_admin() {
3470         global $CFG;
3472         $this->resetAfterTest();
3473         $sink = $this->redirectEmails();
3475         $admin = $CFG->admin;
3476         $CFG->admin = 'custom/admin/path';
3478         $user = $this->getDataGenerator()->create_user(
3479             [
3480                 "username" => "many_-.@characters@_@-..-..",
3481                 "confirmed" => false,
3482                 "email" => 'test@example.com',
3483             ]
3484         );
3485         $confirmationurl = "/admin/test.php?with=params";
3486         $expected = $CFG->wwwroot . "/" . $CFG->admin . "/test.php?with=params&data=/many_-%2E%40characters%40_%40-%2E%2E-%2E%2E";
3488         send_confirmation_email($user, $confirmationurl);
3489         $sink->close();
3490         $messages = $sink->get_messages();
3491         $message = array_shift($messages);
3492         $messagebody = quoted_printable_decode($message->body);
3494         $sink->close();
3495         $this->assertContains($expected, $messagebody);
3497         $CFG->admin = $admin;
3498     }
3501     /**
3502      * Test remove_course_content deletes course contents
3503      * TODO Add asserts to verify other data related to course is deleted as well.
3504      */
3505     public function test_remove_course_contents() {
3507         $this->resetAfterTest();
3509         $course = $this->getDataGenerator()->create_course();
3510         $user = $this->getDataGenerator()->create_user();
3511         $gen = $this->getDataGenerator()->get_plugin_generator('core_notes');
3512         $note = $gen->create_instance(array('courseid' => $course->id, 'userid' => $user->id));
3514         $this->assertNotEquals(false, note_load($note->id));
3515         remove_course_contents($course->id, false);
3516         $this->assertFalse(note_load($note->id));
3517     }
3519     /**
3520      * Test function username_load_fields_from_object().
3521      */
3522     public function test_username_load_fields_from_object() {
3523         $this->resetAfterTest();
3525         // This object represents the information returned from an sql query.
3526         $userinfo = new stdClass();
3527         $userinfo->userid = 1;
3528         $userinfo->username = 'loosebruce';
3529         $userinfo->firstname = 'Bruce';
3530         $userinfo->lastname = 'Campbell';
3531         $userinfo->firstnamephonetic = 'ブルース';
3532         $userinfo->lastnamephonetic = 'カンベッル';
3533         $userinfo->middlename = '';
3534         $userinfo->alternatename = '';
3535         $userinfo->email = '';
3536         $userinfo->picture = 23;
3537         $userinfo->imagealt = 'Michael Jordan draining another basket.';
3538         $userinfo->idnumber = 3982;
3540         // Just user name fields.
3541         $user = new stdClass();
3542         $user = username_load_fields_from_object($user, $userinfo);
3543         $expectedarray = new stdClass();
3544         $expectedarray->firstname = 'Bruce';
3545         $expectedarray->lastname = 'Campbell';
3546         $expectedarray->firstnamephonetic = 'ブルース';
3547         $expectedarray->lastnamephonetic = 'カンベッル';
3548         $expectedarray->middlename = '';
3549         $expectedarray->alternatename = '';
3550         $this->assertEquals($user, $expectedarray);
3552         // User information for showing a picture.
3553         $user = new stdClass();
3554         $additionalfields = explode(',', user_picture::fields());
3555         $user = username_load_fields_from_object($user, $userinfo, null, $additionalfields);
3556         $user->id = $userinfo->userid;
3557         $expectedarray = new stdClass();
3558         $expectedarray->id = 1;
3559         $expectedarray->firstname = 'Bruce';
3560         $expectedarray->lastname = 'Campbell';
3561         $expectedarray->firstnamephonetic = 'ブルース';
3562         $expectedarray->lastnamephonetic = 'カンベッル';
3563         $expectedarray->middlename = '';
3564         $expectedarray->alternatename = '';
3565         $expectedarray->email = '';
3566         $expectedarray->picture = 23;
3567         $expectedarray->imagealt = 'Michael Jordan draining another basket.';
3568         $this->assertEquals($user, $expectedarray);
3570         // Alter the userinfo object to have a prefix.
3571         $userinfo->authorfirstname = 'Bruce';
3572         $userinfo->authorlastname = 'Campbell';
3573         $userinfo->authorfirstnamephonetic = 'ブルース';
3574         $userinfo->authorlastnamephonetic = 'カンベッル';
3575         $userinfo->authormiddlename = '';
3576         $userinfo->authorpicture = 23;
3577         $userinfo->authorimagealt = 'Michael Jordan draining another basket.';
3578         $userinfo->authoremail = 'test@example.com';
3581         // Return an object with user picture information.
3582         $user = new stdClass();
3583         $additionalfields = explode(',', user_picture::fields());
3584         $user = username_load_fields_from_object($user, $userinfo, 'author', $additionalfields);
3585         $user->id = $userinfo->userid;
3586         $expectedarray = new stdClass();
3587         $expectedarray->id = 1;
3588         $expectedarray->firstname = 'Bruce';
3589         $expectedarray->lastname = 'Campbell';
3590         $expectedarray->firstnamephonetic = 'ブルース';
3591         $expectedarray->lastnamephonetic = 'カンベッル';
3592         $expectedarray->middlename = '';
3593         $expectedarray->alternatename = '';
3594         $expectedarray->email = 'test@example.com';
3595         $expectedarray->picture = 23;
3596         $expectedarray->imagealt = 'Michael Jordan draining another basket.';
3597         $this->assertEquals($user, $expectedarray);
3598     }
3600     /**
3601      * Test function count_words().
3602      */
3603     public function test_count_words() {
3604         $count = count_words("one two three'four");
3605         $this->assertEquals(3, $count);
3607         $count = count_words('one+two three’four');
3608         $this->assertEquals(3, $count);
3610         $count = count_words('one"two three-four');
3611         $this->assertEquals(2, $count);
3613         $count = count_words('one@two three_four');
3614         $this->assertEquals(4, $count);
3616         $count = count_words('one\two three/four');
3617         $this->assertEquals(4, $count);
3619         $count = count_words(' one ... two &nbsp; three...four ');
3620         $this->assertEquals(4, $count);
3622         $count = count_words('one.2 3,four');
3623         $this->assertEquals(4, $count);
3625         $count = count_words('1³ £2 €3.45 $6,789');
3626         $this->assertEquals(4, $count);
3628         $count = count_words('one—two ブルース カンベッル');
3629         $this->assertEquals(4, $count);
3631         $count = count_words('one…two ブルース … カンベッル');
3632         $this->assertEquals(4, $count);
3633     }
3634     /**
3635      * Tests the getremoteaddr() function.
3636      */
3637     public function test_getremoteaddr() {
3638         $xforwardedfor = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : null;
3640         $_SERVER['HTTP_X_FORWARDED_FOR'] = '';
3641         $noip = getremoteaddr('1.1.1.1');
3642         $this->assertEquals('1.1.1.1', $noip);
3644         $_SERVER['HTTP_X_FORWARDED_FOR'] = '';
3645         $noip = getremoteaddr();
3646         $this->assertEquals('0.0.0.0', $noip);
3648         $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1';
3649         $singleip = getremoteaddr();
3650         $this->assertEquals('127.0.0.1', $singleip);
3652         $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2';
3653         $twoip = getremoteaddr();
3654         $this->assertEquals('127.0.0.1', $twoip);
3656         $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2, 127.0.0.3';
3657         $threeip = getremoteaddr();
3658         $this->assertEquals('127.0.0.1', $threeip);
3660         $_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1:65535,127.0.0.2';
3661         $portip = getremoteaddr();
3662         $this->assertEquals('127.0.0.1', $portip);
3664         $_SERVER['HTTP_X_FORWARDED_FOR'] = '0:0:0:0:0:0:0:1,127.0.0.2';
3665         $portip = getremoteaddr();
3666         $this->assertEquals('0:0:0:0:0:0:0:1', $portip);
3668         $_SERVER['HTTP_X_FORWARDED_FOR'] = '0::1,127.0.0.2';
3669         $portip = getremoteaddr();
3670         $this->assertEquals('0:0:0:0:0:0:0:1', $portip);
3672         $_SERVER['HTTP_X_FORWARDED_FOR'] = '[0:0:0:0:0:0:0:1]:65535,127.0.0.2';
3673         $portip = getremoteaddr();
3674         $this->assertEquals('0:0:0:0:0:0:0:1', $portip);
3676         $_SERVER['HTTP_X_FORWARDED_FOR'] = $xforwardedfor;
3678     }
3680     /*
3681      * Test emulation of random_bytes() function.
3682      */
3683     public function test_random_bytes_emulate() {
3684         $result = random_bytes_emulate(10);
3685         $this->assertSame(10, strlen($result));
3686         $this->assertnotSame($result, random_bytes_emulate(10));
3688         $result = random_bytes_emulate(21);
3689         $this->assertSame(21, strlen($result));
3690         $this->assertnotSame($result, random_bytes_emulate(21));
3692         $result = random_bytes_emulate(666);
3693         $this->assertSame(666, strlen($result));
3695         $result = random_bytes_emulate(40);
3696         $this->assertSame(40, strlen($result));
3698         $this->assertDebuggingNotCalled();
3700         $result = random_bytes_emulate(0);
3701         $this->assertSame('', $result);
3702         $this->assertDebuggingCalled();
3704         $result = random_bytes_emulate(-1);
3705         $this->assertSame('', $result);
3706         $this->assertDebuggingCalled();
3707     }
3709     /**
3710      * Test function for creation of random strings.
3711      */
3712     public function test_random_string() {
3713         $pool = 'a-zA-Z0-9';
3715         $result = random_string(10);
3716         $this->assertSame(10, strlen($result));
3717         $this->assertRegExp('/^[' . $pool . ']+$/', $result);
3718         $this->assertNotSame($result, random_string(10));
3720         $result = random_string(21);
3721         $this->assertSame(21, strlen($result));
3722         $this->assertRegExp('/^[' . $pool . ']+$/', $result);
3723         $this->assertNotSame($result, random_string(21));
3725         $result = random_string(666);
3726         $this->assertSame(666, strlen($result));
3727         $this->assertRegExp('/^[' . $pool . ']+$/', $result);
3729         $result = random_string();
3730         $this->assertSame(15, strlen($result));
3731         $this->assertRegExp('/^[' . $pool . ']+$/', $result);
3733         $this->assertDebuggingNotCalled();
3735         $result = random_string(0);
3736         $this->assertSame('', $result);
3737         $this->assertDebuggingCalled();
3739         $result = random_string(-1);
3740         $this->assertSame('', $result);
3741         $this->assertDebuggingCalled();
3742     }
3744     /**
3745      * Test function for creation of complex random strings.
3746      */
3747     public function test_complex_random_string() {
3748         $pool = preg_quote('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`~!@#%^&*()_+-=[];,./<>?:{} ', '/');
3750         $result = complex_random_string(10);
3751         $this->assertSame(10, strlen($result));
3752         $this->assertRegExp('/^[' . $pool . ']+$/', $result);
3753         $this->assertNotSame($result, complex_random_string(10));
3755         $result = complex_random_string(21);
3756         $this->assertSame(21, strlen($result));
3757         $this->assertRegExp('/^[' . $pool . ']+$/', $result);
3758         $this->assertNotSame($result, complex_random_string(21));
3760         $result = complex_random_string(666);
3761