Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.6.x will end 11 November 2019 (12 months).
  • Bug fixes for security issues in 3.6.x will end 11 May 2020 (18 months) - Support has ended.
  • minimum PHP 7.0.0 Note: minimum PHP version has increased since Moodle 3.3. PHP 7.1.x and 7.2.x are supported too. PHP 7.3.x support is being implemented (@ MDL-63420) and not ready for production with this release.
  • Differences Between: [Versions 35 and 36] [Versions 36 and 310] [Versions 36 and 311] [Versions 36 and 37] [Versions 36 and 38] [Versions 36 and 39]

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