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