PageRenderTime 75ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/tests/moodlelib_test.php

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