Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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 setuplib.php
  19   *
  20   * @package   core
  21   * @category  phpunit
  22   * @copyright 2012 The Open University
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  /**
  29   * Unit tests for setuplib.php
  30   *
  31   * @copyright 2012 The Open University
  32   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33   */
  34  class core_setuplib_testcase extends advanced_testcase {
  35  
  36      /**
  37       * Test get_docs_url_standard in the normal case when we should link to Moodle docs.
  38       */
  39      public function test_get_docs_url_standard() {
  40          global $CFG;
  41          if (empty($CFG->docroot)) {
  42              $docroot = 'http://docs.moodle.org/';
  43          } else {
  44              $docroot = $CFG->docroot;
  45          }
  46          $this->assertRegExp('~^' . preg_quote($docroot, '') . '/\d{2}/' . current_language() . '/course/editing$~',
  47                  get_docs_url('course/editing'));
  48      }
  49  
  50      /**
  51       * Test get_docs_url_standard in the special case of an absolute HTTP URL.
  52       */
  53      public function test_get_docs_url_http() {
  54          $url = 'http://moodle.org/';
  55          $this->assertEquals($url, get_docs_url($url));
  56      }
  57  
  58      /**
  59       * Test get_docs_url_standard in the special case of an absolute HTTPS URL.
  60       */
  61      public function test_get_docs_url_https() {
  62          $url = 'https://moodle.org/';
  63          $this->assertEquals($url, get_docs_url($url));
  64      }
  65  
  66      /**
  67       * Test get_docs_url_standard in the special case of a link relative to wwwroot.
  68       */
  69      public function test_get_docs_url_wwwroot() {
  70          global $CFG;
  71          $this->assertSame($CFG->wwwroot . '/lib/tests/setuplib_test.php',
  72                  get_docs_url('%%WWWROOT%%/lib/tests/setuplib_test.php'));
  73      }
  74  
  75      /**
  76       * Test if get_exception_info() removes file system paths.
  77       */
  78      public function test_exception_info_removes_serverpaths() {
  79          global $CFG;
  80  
  81          // This doesn't test them all possible ones, but these are set for unit tests.
  82          $cfgnames = array('dataroot', 'dirroot', 'tempdir', 'backuptempdir', 'cachedir', 'localcachedir');
  83  
  84          $fixture  = '';
  85          $expected = '';
  86          foreach ($cfgnames as $cfgname) {
  87              if (!empty($CFG->$cfgname)) {
  88                  $fixture  .= $CFG->$cfgname.' ';
  89                  $expected .= "[$cfgname] ";
  90              }
  91          }
  92          $exception     = new moodle_exception('generalexceptionmessage', 'error', '', $fixture, $fixture);
  93          $exceptioninfo = get_exception_info($exception);
  94  
  95          $this->assertContains($expected, $exceptioninfo->message, 'Exception message does not contain system paths');
  96          $this->assertContains($expected, $exceptioninfo->debuginfo, 'Exception debug info does not contain system paths');
  97      }
  98  
  99      public function test_localcachedir() {
 100          global $CFG;
 101  
 102          $this->resetAfterTest(true);
 103  
 104          // Test default location - can not be modified in phpunit tests because we override everything in config.php.
 105          $this->assertSame("$CFG->dataroot/localcache", $CFG->localcachedir);
 106  
 107          $this->setCurrentTimeStart();
 108          $timestampfile = "$CFG->localcachedir/.lastpurged";
 109  
 110          // Delete existing localcache directory, as this is testing first call
 111          // to make_localcache_directory.
 112          remove_dir($CFG->localcachedir, true);
 113          $dir = make_localcache_directory('', false);
 114          $this->assertSame($CFG->localcachedir, $dir);
 115          $this->assertFileNotExists("$CFG->localcachedir/.htaccess");
 116          $this->assertFileExists($timestampfile);
 117          $this->assertTimeCurrent(filemtime($timestampfile));
 118  
 119          $dir = make_localcache_directory('test/test', false);
 120          $this->assertSame("$CFG->localcachedir/test/test", $dir);
 121  
 122          // Test custom location.
 123          $CFG->localcachedir = "$CFG->dataroot/testlocalcache";
 124          $this->setCurrentTimeStart();
 125          $timestampfile = "$CFG->localcachedir/.lastpurged";
 126          $this->assertFileNotExists($timestampfile);
 127  
 128          $dir = make_localcache_directory('', false);
 129          $this->assertSame($CFG->localcachedir, $dir);
 130          $this->assertFileExists("$CFG->localcachedir/.htaccess");
 131          $this->assertFileExists($timestampfile);
 132          $this->assertTimeCurrent(filemtime($timestampfile));
 133  
 134          $dir = make_localcache_directory('test', false);
 135          $this->assertSame("$CFG->localcachedir/test", $dir);
 136  
 137          $prevtime = filemtime($timestampfile);
 138          $dir = make_localcache_directory('pokus', false);
 139          $this->assertSame("$CFG->localcachedir/pokus", $dir);
 140          $this->assertSame($prevtime, filemtime($timestampfile));
 141  
 142          // Test purging.
 143          $testfile = "$CFG->localcachedir/test/test.txt";
 144          $this->assertTrue(touch($testfile));
 145  
 146          $now = $this->setCurrentTimeStart();
 147          set_config('localcachedirpurged', $now - 2);
 148          purge_all_caches();
 149          $this->assertFileNotExists($testfile);
 150          $this->assertFileNotExists(dirname($testfile));
 151          $this->assertFileExists($timestampfile);
 152          $this->assertTimeCurrent(filemtime($timestampfile));
 153          $this->assertTimeCurrent($CFG->localcachedirpurged);
 154  
 155          // Simulates purge_all_caches() on another server node.
 156          make_localcache_directory('test', false);
 157          $this->assertTrue(touch($testfile));
 158          set_config('localcachedirpurged', $now - 1);
 159          $this->assertTrue(touch($timestampfile, $now - 2));
 160          clearstatcache();
 161          $this->assertSame($now - 2, filemtime($timestampfile));
 162  
 163          $this->setCurrentTimeStart();
 164          $dir = make_localcache_directory('', false);
 165          $this->assertSame("$CFG->localcachedir", $dir);
 166          $this->assertFileNotExists($testfile);
 167          $this->assertFileNotExists(dirname($testfile));
 168          $this->assertFileExists($timestampfile);
 169          $this->assertTimeCurrent(filemtime($timestampfile));
 170      }
 171  
 172      public function test_make_unique_directory_basedir_is_file() {
 173          global $CFG;
 174  
 175          // Start with a file instead of a directory.
 176          $base = $CFG->tempdir . DIRECTORY_SEPARATOR . md5(microtime(true) + rand());
 177          touch($base);
 178  
 179          // First the false test.
 180          $this->assertFalse(make_unique_writable_directory($base, false));
 181  
 182          // Now check for exception.
 183          $this->expectException('invalid_dataroot_permissions');
 184          $this->expectExceptionMessage($base . ' is not writable. Unable to create a unique directory within it.');
 185          make_unique_writable_directory($base);
 186  
 187          unlink($base);
 188      }
 189  
 190      public function test_make_unique_directory() {
 191          global $CFG;
 192  
 193          // Create directories should be both directories, and writable.
 194          $firstdir = make_unique_writable_directory($CFG->tempdir);
 195          $this->assertTrue(is_dir($firstdir));
 196          $this->assertTrue(is_writable($firstdir));
 197  
 198          $seconddir = make_unique_writable_directory($CFG->tempdir);
 199          $this->assertTrue(is_dir($seconddir));
 200          $this->assertTrue(is_writable($seconddir));
 201  
 202          // Directories should be different each iteration.
 203          $this->assertNotEquals($firstdir, $seconddir);
 204      }
 205  
 206      public function test_get_request_storage_directory() {
 207          // Making a call to get_request_storage_directory should always give the same result.
 208          $firstdir = get_request_storage_directory();
 209          $seconddir = get_request_storage_directory();
 210          $this->assertTrue(is_dir($firstdir));
 211          $this->assertEquals($firstdir, $seconddir);
 212  
 213          // Removing the directory and calling get_request_storage_directory() again should cause a new directory to be created.
 214          remove_dir($firstdir);
 215          $this->assertFalse(file_exists($firstdir));
 216          $this->assertFalse(is_dir($firstdir));
 217  
 218          $thirddir = get_request_storage_directory();
 219          $this->assertTrue(is_dir($thirddir));
 220          $this->assertNotEquals($firstdir, $thirddir);
 221  
 222          // Removing it and replacing it with a file should cause it to be regenerated again.
 223          remove_dir($thirddir);
 224          $this->assertFalse(file_exists($thirddir));
 225          $this->assertFalse(is_dir($thirddir));
 226          touch($thirddir);
 227          $this->assertTrue(file_exists($thirddir));
 228          $this->assertFalse(is_dir($thirddir));
 229  
 230          $fourthdir = get_request_storage_directory();
 231          $this->assertTrue(is_dir($fourthdir));
 232          $this->assertNotEquals($thirddir, $fourthdir);
 233      }
 234  
 235  
 236      public function test_make_request_directory() {
 237          // Every request directory should be unique.
 238          $firstdir   = make_request_directory();
 239          $seconddir  = make_request_directory();
 240          $thirddir   = make_request_directory();
 241          $fourthdir  = make_request_directory();
 242  
 243          $this->assertNotEquals($firstdir,   $seconddir);
 244          $this->assertNotEquals($firstdir,   $thirddir);
 245          $this->assertNotEquals($firstdir,   $fourthdir);
 246          $this->assertNotEquals($seconddir,  $thirddir);
 247          $this->assertNotEquals($seconddir,  $fourthdir);
 248          $this->assertNotEquals($thirddir,   $fourthdir);
 249  
 250          // They should also all be within the request storage directory.
 251          $requestdir = get_request_storage_directory();
 252          $this->assertEquals(0, strpos($firstdir,    $requestdir));
 253          $this->assertEquals(0, strpos($seconddir,   $requestdir));
 254          $this->assertEquals(0, strpos($thirddir,    $requestdir));
 255          $this->assertEquals(0, strpos($fourthdir,   $requestdir));
 256  
 257          // Removing the requestdir should mean that new request directories are still created successfully.
 258          remove_dir($requestdir);
 259          $this->assertFalse(file_exists($requestdir));
 260          $this->assertFalse(is_dir($requestdir));
 261  
 262          $fifthdir   = make_request_directory();
 263          $this->assertNotEquals($firstdir,   $fifthdir);
 264          $this->assertNotEquals($seconddir,  $fifthdir);
 265          $this->assertNotEquals($thirddir,   $fifthdir);
 266          $this->assertNotEquals($fourthdir,  $fifthdir);
 267          $this->assertTrue(is_dir($fifthdir));
 268          $this->assertFalse(strpos($fifthdir, $requestdir));
 269  
 270          // And it should be within the new request directory.
 271          $newrequestdir = get_request_storage_directory();
 272          $this->assertEquals(0, strpos($fifthdir, $newrequestdir));
 273      }
 274  
 275      public function test_merge_query_params() {
 276          $original = array(
 277              'id' => '1',
 278              'course' => '2',
 279              'action' => 'delete',
 280              'grade' => array(
 281                  0 => 'a',
 282                  1 => 'b',
 283                  2 => 'c',
 284              ),
 285              'items' => array(
 286                  'a' => 'aa',
 287                  'b' => 'bb',
 288              ),
 289              'mix' => array(
 290                  0 => '2',
 291              ),
 292              'numerical' => array(
 293                  '2' => array('a' => 'b'),
 294                  '1' => '2',
 295              ),
 296          );
 297  
 298          $chunk = array(
 299              'numerical' => array(
 300                  '0' => 'z',
 301                  '2' => array('d' => 'e'),
 302              ),
 303              'action' => 'create',
 304              'next' => '2',
 305              'grade' => array(
 306                  0 => 'e',
 307                  1 => 'f',
 308                  2 => 'g',
 309              ),
 310              'mix' => 'mix',
 311          );
 312  
 313          $expected = array(
 314              'id' => '1',
 315              'course' => '2',
 316              'action' => 'create',
 317              'grade' => array(
 318                  0 => 'a',
 319                  1 => 'b',
 320                  2 => 'c',
 321                  3 => 'e',
 322                  4 => 'f',
 323                  5 => 'g',
 324              ),
 325              'items' => array(
 326                  'a' => 'aa',
 327                  'b' => 'bb',
 328              ),
 329              'mix' => 'mix',
 330              'numerical' => array(
 331                  '2' => array('a' => 'b', 'd' => 'e'),
 332                  '1' => '2',
 333                  '0' => 'z',
 334              ),
 335              'next' => '2',
 336          );
 337  
 338          $array = $original;
 339          merge_query_params($array, $chunk);
 340  
 341          $this->assertSame($expected, $array);
 342          $this->assertNotSame($original, $array);
 343  
 344          $query = "id=1&course=2&action=create&grade%5B%5D=a&grade%5B%5D=b&grade%5B%5D=c&grade%5B%5D=e&grade%5B%5D=f&grade%5B%5D=g&items%5Ba%5D=aa&items%5Bb%5D=bb&mix=mix&numerical%5B2%5D%5Ba%5D=b&numerical%5B2%5D%5Bd%5D=e&numerical%5B1%5D=2&numerical%5B0%5D=z&next=2";
 345          $decoded = array();
 346          parse_str($query, $decoded);
 347          $this->assertSame($expected, $decoded);
 348  
 349          // Prove that we cannot use array_merge_recursive() instead.
 350          $this->assertNotSame($expected, array_merge_recursive($original, $chunk));
 351      }
 352  
 353      /**
 354       * Test the link processed by get_exception_info().
 355       */
 356      public function test_get_exception_info_link() {
 357          global $CFG, $SESSION;
 358  
 359          $httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
 360  
 361          // Simple local URL.
 362          $url = $CFG->wwwroot . '/something/here?really=yes';
 363          $exception = new moodle_exception('none', 'error', $url);
 364          $infos = $this->get_exception_info($exception);
 365          $this->assertSame($url, $infos->link);
 366  
 367          // Relative local URL.
 368          $url = '/something/here?really=yes';
 369          $exception = new moodle_exception('none', 'error', $url);
 370          $infos = $this->get_exception_info($exception);
 371          $this->assertSame($CFG->wwwroot . '/', $infos->link);
 372  
 373          // HTTPS URL when login HTTPS is not enabled (default) and site is HTTP.
 374          $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot);
 375          $url = $httpswwwroot . '/something/here?really=yes';
 376          $exception = new moodle_exception('none', 'error', $url);
 377          $infos = $this->get_exception_info($exception);
 378          $this->assertSame($CFG->wwwroot . '/', $infos->link);
 379  
 380          // HTTPS URL when login HTTPS is not enabled and site is HTTPS.
 381          $CFG->wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
 382          $url = $httpswwwroot . '/something/here?really=yes';
 383          $exception = new moodle_exception('none', 'error', $url);
 384          $infos = $this->get_exception_info($exception);
 385          $this->assertSame($url, $infos->link);
 386  
 387          // External HTTP URL.
 388          $url = 'http://moodle.org/something/here?really=yes';
 389          $exception = new moodle_exception('none', 'error', $url);
 390          $infos = $this->get_exception_info($exception);
 391          $this->assertSame($CFG->wwwroot . '/', $infos->link);
 392  
 393          // External HTTPS URL.
 394          $url = 'https://moodle.org/something/here?really=yes';
 395          $exception = new moodle_exception('none', 'error', $url);
 396          $infos = $this->get_exception_info($exception);
 397          $this->assertSame($CFG->wwwroot . '/', $infos->link);
 398  
 399          // External URL containing local URL.
 400          $url = 'http://moodle.org/something/here?' . $CFG->wwwroot;
 401          $exception = new moodle_exception('none', 'error', $url);
 402          $infos = $this->get_exception_info($exception);
 403          $this->assertSame($CFG->wwwroot . '/', $infos->link);
 404  
 405          // Internal link from fromurl.
 406          $SESSION->fromurl = $url = $CFG->wwwroot . '/something/here?really=yes';
 407          $exception = new moodle_exception('none');
 408          $infos = $this->get_exception_info($exception);
 409          $this->assertSame($url, $infos->link);
 410  
 411          // Internal HTTPS link from fromurl.
 412          $SESSION->fromurl = $url = $httpswwwroot . '/something/here?really=yes';
 413          $exception = new moodle_exception('none');
 414          $infos = $this->get_exception_info($exception);
 415          $this->assertSame($url, $infos->link);
 416  
 417          // External link from fromurl.
 418          $SESSION->fromurl = 'http://moodle.org/something/here?really=yes';
 419          $exception = new moodle_exception('none');
 420          $infos = $this->get_exception_info($exception);
 421          $this->assertSame($CFG->wwwroot . '/', $infos->link);
 422  
 423          // External HTTPS link from fromurl.
 424          $SESSION->fromurl = 'https://moodle.org/something/here?really=yes';
 425          $exception = new moodle_exception('none');
 426          $infos = $this->get_exception_info($exception);
 427          $this->assertSame($CFG->wwwroot . '/', $infos->link);
 428  
 429          $SESSION->fromurl = '';
 430      }
 431  
 432      /**
 433       * Wrapper to call {@link get_exception_info()}.
 434       *
 435       * @param  Exception $ex An exception.
 436       * @return stdClass of information.
 437       */
 438      public function get_exception_info($ex) {
 439          try {
 440              throw $ex;
 441          } catch (moodle_exception $e) {
 442              return get_exception_info($e);
 443          }
 444      }
 445  
 446      /**
 447       * Data provider for test_get_real_size().
 448       *
 449       * @return array An array of arrays contain test data
 450       */
 451      public function data_for_test_get_real_size() {
 452          return array(
 453              array('8KB', 8192),
 454              array('8Kb', 8192),
 455              array('8K', 8192),
 456              array('8k', 8192),
 457              array('50MB', 52428800),
 458              array('50Mb', 52428800),
 459              array('50M', 52428800),
 460              array('50m', 52428800),
 461              array('8Gb', 8589934592),
 462              array('8GB', 8589934592),
 463              array('8G', 8589934592),
 464          );
 465      }
 466  
 467      /**
 468       * Test the get_real_size() function.
 469       *
 470       * @dataProvider data_for_test_get_real_size
 471       *
 472       * @param string $input the input for get_real_size()
 473       * @param int $expectedbytes the expected bytes
 474       */
 475      public function test_get_real_size($input, $expectedbytes) {
 476          $this->assertEquals($expectedbytes, get_real_size($input));
 477      }
 478  
 479      /**
 480       * Validate the given V4 UUID.
 481       *
 482       * @param string $value The candidate V4 UUID
 483       * @return bool True if valid; otherwise, false.
 484       */
 485      protected static function is_valid_uuid_v4($value) {
 486          // Version 4 UUIDs have the form xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxx
 487          // where x is any hexadecimal digit and Y is one of 8, 9, aA, or bB.
 488          // First, the size is 36 (32 + 4 dashes).
 489          if (strlen($value) != 36) {
 490              return false;
 491          }
 492          // Finally, check the format.
 493          $uuidv4pattern = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i';
 494          return (preg_match($uuidv4pattern, $value) === 1);
 495      }
 496  
 497      /**
 498       * Test the \core\uuid::generate_uuid_via_pecl_uuid_extension() function.
 499       */
 500      public function test_core_uuid_generate_uuid_via_pecl_uuid_extension() {
 501          if (!extension_loaded('uuid')) {
 502              $this->markTestSkipped("PHP 'uuid' extension not loaded.");
 503          }
 504          if (!function_exists('uuid_time')) {
 505              $this->markTestSkipped("PHP PECL 'uuid' extension not loaded.");
 506          }
 507  
 508          // The \core\uuid::generate_uuid_via_pecl_uuid_extension static method is protected. Use Reflection to call the method.
 509          $method = new ReflectionMethod('\core\uuid', 'generate_uuid_via_pecl_uuid_extension');
 510          $method->setAccessible(true);
 511          $uuid = $method->invoke(null);
 512          $this->assertTrue(self::is_valid_uuid_v4($uuid), "Invalid v4 uuid: '$uuid'");
 513      }
 514  
 515      /**
 516       * Test the \core\uuid::generate_uuid_via_random_bytes() function.
 517       */
 518      public function test_core_uuid_generate_uuid_via_random_bytes() {
 519          try {
 520              random_bytes(1);
 521          } catch (Exception $e) {
 522              $this->markTestSkipped('No source of entropy for random_bytes. ' . $e->getMessage());
 523          }
 524  
 525          // The \core\uuid::generate_uuid_via_random_bytes static method is protected. Use Reflection to call the method.
 526          $method = new ReflectionMethod('\core\uuid', 'generate_uuid_via_random_bytes');
 527          $method->setAccessible(true);
 528          $uuid = $method->invoke(null);
 529          $this->assertTrue(self::is_valid_uuid_v4($uuid), "Invalid v4 uuid: '$uuid'");
 530      }
 531  
 532      /**
 533       * Test the \core\uuid::generate() function.
 534       */
 535      public function test_core_uuid_generate() {
 536          $uuid = \core\uuid::generate();
 537          $this->assertTrue(self::is_valid_uuid_v4($uuid), "Invalid v4 UUID: '$uuid'");
 538      }
 539  }