Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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 those parts of adminlib.php that implement the admin tree
  19   * functionality.
  20   *
  21   * @package     core
  22   * @category    phpunit
  23   * @copyright   2013 David Mudrak <david@moodle.com>
  24   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  global $CFG;
  30  require_once($CFG->libdir.'/adminlib.php');
  31  
  32  /**
  33   * Provides the unit tests for admin tree functionality.
  34   */
  35  class core_admintree_testcase extends advanced_testcase {
  36  
  37      /**
  38       * Adding nodes into the admin tree.
  39       */
  40      public function test_add_nodes() {
  41  
  42          $tree = new admin_root(true);
  43          $tree->add('root', $one = new admin_category('one', 'One'));
  44          $tree->add('root', new admin_category('three', 'Three'));
  45          $tree->add('one', new admin_category('one-one', 'One-one'));
  46          $tree->add('one', new admin_category('one-three', 'One-three'));
  47  
  48          // Check the order of nodes in the root.
  49          $map = array();
  50          foreach ($tree->children as $child) {
  51              $map[] = $child->name;
  52          }
  53          $this->assertEquals(array('one', 'three'), $map);
  54  
  55          // Insert a node into the middle.
  56          $tree->add('root', new admin_category('two', 'Two'), 'three');
  57          $map = array();
  58          foreach ($tree->children as $child) {
  59              $map[] = $child->name;
  60          }
  61          $this->assertEquals(array('one', 'two', 'three'), $map);
  62  
  63          // Non-existing sibling.
  64          $tree->add('root', new admin_category('four', 'Four'), 'five');
  65          $this->assertDebuggingCalled('Sibling five not found', DEBUG_DEVELOPER);
  66  
  67          $tree->add('root', new admin_category('five', 'Five'));
  68          $map = array();
  69          foreach ($tree->children as $child) {
  70              $map[] = $child->name;
  71          }
  72          $this->assertEquals(array('one', 'two', 'three', 'four', 'five'), $map);
  73  
  74          // Insert a node into the middle of the subcategory.
  75          $tree->add('one', new admin_category('one-two', 'One-two'), 'one-three');
  76          $map = array();
  77          foreach ($one->children as $child) {
  78              $map[] = $child->name;
  79          }
  80          $this->assertEquals(array('one-one', 'one-two', 'one-three'), $map);
  81  
  82          // Check just siblings, not parents or children.
  83          $tree->add('one', new admin_category('one-four', 'One-four'), 'one');
  84          $this->assertDebuggingCalled('Sibling one not found', DEBUG_DEVELOPER);
  85  
  86          $tree->add('root', new admin_category('six', 'Six'), 'one-two');
  87          $this->assertDebuggingCalled('Sibling one-two not found', DEBUG_DEVELOPER);
  88  
  89          // Me! Me! I wanna be first!
  90          $tree->add('root', new admin_externalpage('zero', 'Zero', 'http://foo.bar'), 'one');
  91          $map = array();
  92          foreach ($tree->children as $child) {
  93              $map[] = $child->name;
  94          }
  95          $this->assertEquals(array('zero', 'one', 'two', 'three', 'four', 'five', 'six'), $map);
  96      }
  97  
  98      public function test_add_nodes_before_invalid1() {
  99          $tree = new admin_root(true);
 100          $this->expectException(coding_exception::class);
 101          $tree->add('root', new admin_externalpage('foo', 'Foo', 'http://foo.bar'), array('moodle:site/config'));
 102      }
 103  
 104      public function test_add_nodes_before_invalid2() {
 105          $tree = new admin_root(true);
 106          $this->expectException(coding_exception::class);
 107          $tree->add('root', new admin_category('bar', 'Bar'), '');
 108      }
 109  
 110      /**
 111       * Test that changes to config trigger events.
 112       */
 113      public function test_config_log_created_event() {
 114          global $DB;
 115          $this->resetAfterTest();
 116          $this->setAdminUser();
 117  
 118          $adminroot = new admin_root(true);
 119          $adminroot->add('root', $one = new admin_category('one', 'One'));
 120          $page = new admin_settingpage('page', 'Page');
 121          $page->add(new admin_setting_configtext('text1', 'Text 1', '', ''));
 122          $page->add(new admin_setting_configpasswordunmask('pass1', 'Password 1', '', ''));
 123          $adminroot->add('one', $page);
 124  
 125          $sink = $this->redirectEvents();
 126          $data = array('s__text1' => 'sometext', 's__pass1' => '');
 127          $this->save_config_data($adminroot, $data);
 128  
 129          $events = $sink->get_events();
 130          $sink->close();
 131          $event = array_pop($events);
 132          $this->assertInstanceOf('\core\event\config_log_created', $event);
 133  
 134          $sink = $this->redirectEvents();
 135          $data = array('s__text1'=>'other', 's__pass1'=>'nice password');
 136          $count = $this->save_config_data($adminroot, $data);
 137  
 138          $events = $sink->get_events();
 139          $sink->close();
 140          $event = array_pop($events);
 141          $this->assertInstanceOf('\core\event\config_log_created', $event);
 142          // Verify password was nuked.
 143          $this->assertNotEquals($event->other['value'], 'nice password');
 144  
 145      }
 146  
 147      /**
 148       * Testing whether a configexecutable setting is executable.
 149       */
 150      public function test_admin_setting_configexecutable() {
 151          global $CFG;
 152          $this->resetAfterTest();
 153  
 154          $CFG->theme = 'classic';
 155          $executable = new admin_setting_configexecutable('test1', 'Text 1', 'Help Path', '');
 156  
 157          // Check for an invalid path.
 158          $result = $executable->output_html($CFG->dirroot . '/lib/tests/other/file_does_not_exist');
 159          $this->assertRegexp('/class="text-danger"/', $result);
 160  
 161          // Check for a directory.
 162          $result = $executable->output_html($CFG->dirroot);
 163          $this->assertRegexp('/class="text-danger"/', $result);
 164  
 165          // Check for a file which is not executable.
 166          $result = $executable->output_html($CFG->dirroot . '/filter/tex/readme_moodle.txt');
 167          $this->assertRegexp('/class="text-danger"/', $result);
 168  
 169          // Check for an executable file.
 170          if ($CFG->ostype == 'WINDOWS') {
 171              $filetocheck = 'mimetex.exe';
 172          } else {
 173              $filetocheck = 'mimetex.darwin';
 174          }
 175          $result = $executable->output_html($CFG->dirroot . '/filter/tex/' . $filetocheck);
 176          $this->assertRegexp('/class="text-success"/', $result);
 177  
 178          // Check for no file specified.
 179          $result = $executable->output_html('');
 180          $this->assertRegexp('/name="s__test1"/', $result);
 181          $this->assertRegexp('/value=""/', $result);
 182      }
 183  
 184      /**
 185       * Saving of values.
 186       */
 187      public function test_config_logging() {
 188          global $DB;
 189          $this->resetAfterTest();
 190          $this->setAdminUser();
 191  
 192          $DB->delete_records('config_log', array());
 193  
 194          $adminroot = new admin_root(true);
 195          $adminroot->add('root', $one = new admin_category('one', 'One'));
 196          $page = new admin_settingpage('page', 'Page');
 197          $page->add(new admin_setting_configtext('text1', 'Text 1', '', ''));
 198          $page->add(new admin_setting_configpasswordunmask('pass1', 'Password 1', '', ''));
 199          $adminroot->add('one', $page);
 200  
 201          $this->assertEmpty($DB->get_records('config_log'));
 202          $data = array('s__text1'=>'sometext', 's__pass1'=>'');
 203          $count = $this->save_config_data($adminroot, $data);
 204  
 205          $this->assertEquals(2, $count);
 206          $records = $DB->get_records('config_log', array(), 'id asc');
 207          $this->assertCount(2, $records);
 208          reset($records);
 209          $record = array_shift($records);
 210          $this->assertNull($record->plugin);
 211          $this->assertSame('text1', $record->name);
 212          $this->assertNull($record->oldvalue);
 213          $this->assertSame('sometext', $record->value);
 214          $record = array_shift($records);
 215          $this->assertNull($record->plugin);
 216          $this->assertSame('pass1', $record->name);
 217          $this->assertNull($record->oldvalue);
 218          $this->assertSame('', $record->value);
 219  
 220          $DB->delete_records('config_log', array());
 221          $data = array('s__text1'=>'other', 's__pass1'=>'nice password');
 222          $count = $this->save_config_data($adminroot, $data);
 223  
 224          $this->assertEquals(2, $count);
 225          $records = $DB->get_records('config_log', array(), 'id asc');
 226          $this->assertCount(2, $records);
 227          reset($records);
 228          $record = array_shift($records);
 229          $this->assertNull($record->plugin);
 230          $this->assertSame('text1', $record->name);
 231          $this->assertSame('sometext', $record->oldvalue);
 232          $this->assertSame('other', $record->value);
 233          $record = array_shift($records);
 234          $this->assertNull($record->plugin);
 235          $this->assertSame('pass1', $record->name);
 236          $this->assertSame('', $record->oldvalue);
 237          $this->assertSame('********', $record->value);
 238  
 239          $DB->delete_records('config_log', array());
 240          $data = array('s__text1'=>'', 's__pass1'=>'');
 241          $count = $this->save_config_data($adminroot, $data);
 242  
 243          $this->assertEquals(2, $count);
 244          $records = $DB->get_records('config_log', array(), 'id asc');
 245          $this->assertCount(2, $records);
 246          reset($records);
 247          $record = array_shift($records);
 248          $this->assertNull($record->plugin);
 249          $this->assertSame('text1', $record->name);
 250          $this->assertSame('other', $record->oldvalue);
 251          $this->assertSame('', $record->value);
 252          $record = array_shift($records);
 253          $this->assertNull($record->plugin);
 254          $this->assertSame('pass1', $record->name);
 255          $this->assertSame('********', $record->oldvalue);
 256          $this->assertSame('', $record->value);
 257      }
 258  
 259      protected function save_config_data(admin_root $adminroot, array $data) {
 260          $adminroot->errors = array();
 261  
 262          $settings = admin_find_write_settings($adminroot, $data);
 263  
 264          $count = 0;
 265          foreach ($settings as $fullname=>$setting) {
 266              /** @var $setting admin_setting */
 267              $original = $setting->get_setting();
 268              $error = $setting->write_setting($data[$fullname]);
 269              if ($error !== '') {
 270                  $adminroot->errors[$fullname] = new stdClass();
 271                  $adminroot->errors[$fullname]->data  = $data[$fullname];
 272                  $adminroot->errors[$fullname]->id    = $setting->get_id();
 273                  $adminroot->errors[$fullname]->error = $error;
 274              } else {
 275                  $setting->write_setting_flags($data);
 276              }
 277              if ($setting->post_write_settings($original)) {
 278                  $count++;
 279              }
 280          }
 281  
 282          return $count;
 283      }
 284  
 285      public function test_preventexecpath() {
 286          $this->resetAfterTest();
 287  
 288          set_config('preventexecpath', 0);
 289          set_config('execpath', null, 'abc_cde');
 290          $this->assertFalse(get_config('abc_cde', 'execpath'));
 291          $setting = new admin_setting_configexecutable('abc_cde/execpath', 'some desc', '', '/xx/yy');
 292          $setting->write_setting('/oo/pp');
 293          $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
 294  
 295          // Prevent changes.
 296          set_config('preventexecpath', 1);
 297          $setting->write_setting('/mm/nn');
 298          $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
 299  
 300          // Use default in install.
 301          set_config('execpath', null, 'abc_cde');
 302          $setting->write_setting('/mm/nn');
 303          $this->assertSame('/xx/yy', get_config('abc_cde', 'execpath'));
 304  
 305          // Use empty value if no default.
 306          $setting = new admin_setting_configexecutable('abc_cde/execpath', 'some desc', '', null);
 307          set_config('execpath', null, 'abc_cde');
 308          $setting->write_setting('/mm/nn');
 309          $this->assertSame('', get_config('abc_cde', 'execpath'));
 310  
 311          // This also affects admin_setting_configfile and admin_setting_configdirectory.
 312  
 313          set_config('preventexecpath', 0);
 314          set_config('execpath', null, 'abc_cde');
 315          $this->assertFalse(get_config('abc_cde', 'execpath'));
 316          $setting = new admin_setting_configfile('abc_cde/execpath', 'some desc', '', '/xx/yy');
 317          $setting->write_setting('/oo/pp');
 318          $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
 319  
 320          // Prevent changes.
 321          set_config('preventexecpath', 1);
 322          $setting->write_setting('/mm/nn');
 323          $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
 324  
 325          // Use default in install.
 326          set_config('execpath', null, 'abc_cde');
 327          $setting->write_setting('/mm/nn');
 328          $this->assertSame('/xx/yy', get_config('abc_cde', 'execpath'));
 329  
 330          // Use empty value if no default.
 331          $setting = new admin_setting_configfile('abc_cde/execpath', 'some desc', '', null);
 332          set_config('execpath', null, 'abc_cde');
 333          $setting->write_setting('/mm/nn');
 334          $this->assertSame('', get_config('abc_cde', 'execpath'));
 335  
 336          set_config('preventexecpath', 0);
 337          set_config('execpath', null, 'abc_cde');
 338          $this->assertFalse(get_config('abc_cde', 'execpath'));
 339          $setting = new admin_setting_configdirectory('abc_cde/execpath', 'some desc', '', '/xx/yy');
 340          $setting->write_setting('/oo/pp');
 341          $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
 342  
 343          // Prevent changes.
 344          set_config('preventexecpath', 1);
 345          $setting->write_setting('/mm/nn');
 346          $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
 347  
 348          // Use default in install.
 349          set_config('execpath', null, 'abc_cde');
 350          $setting->write_setting('/mm/nn');
 351          $this->assertSame('/xx/yy', get_config('abc_cde', 'execpath'));
 352  
 353          // Use empty value if no default.
 354          $setting = new admin_setting_configdirectory('abc_cde/execpath', 'some desc', '', null);
 355          set_config('execpath', null, 'abc_cde');
 356          $setting->write_setting('/mm/nn');
 357          $this->assertSame('', get_config('abc_cde', 'execpath'));
 358      }
 359  
 360      /**
 361       * Test setting for blocked hosts
 362       *
 363       * For testing the admin settings element only. Test for blocked hosts functionality can be found
 364       * in lib/tests/curl_security_helper_test.php
 365       */
 366      public function test_mixedhostiplist() {
 367          $this->resetAfterTest();
 368  
 369          $adminsetting = new admin_setting_configmixedhostiplist('abc_cde/hostiplist', 'some desc', '', '');
 370  
 371          // Test valid settings.
 372          $validsimplesettings = [
 373              'localhost',
 374              "localhost\n127.0.0.1",
 375              '192.168.10.1',
 376              '0:0:0:0:0:0:0:1',
 377              '::1',
 378              'fe80::',
 379              '231.54.211.0/20',
 380              'fe80::/64',
 381              '231.3.56.10-20',
 382              'fe80::1111-bbbb',
 383              '*.example.com',
 384              '*.sub.example.com',
 385          ];
 386  
 387          foreach ($validsimplesettings as $setting) {
 388              $errormessage = $adminsetting->write_setting($setting);
 389              $this->assertEmpty($errormessage, $errormessage);
 390              $this->assertSame($setting, get_config('abc_cde', 'hostiplist'));
 391              $this->assertSame($setting, $adminsetting->get_setting());
 392          }
 393  
 394          // Test valid international site names.
 395          $valididnsettings = [
 396              'правительство.рф' => 'xn--80aealotwbjpid2k.xn--p1ai',
 397              'faß.de' => 'xn--fa-hia.de',
 398              'ß.ß' => 'xn--zca.xn--zca',
 399              '*.tharkûn.com' => '*.xn--tharkn-0ya.com',
 400          ];
 401  
 402          foreach ($valididnsettings as $setting => $encodedsetting) {
 403              $errormessage = $adminsetting->write_setting($setting);
 404              $this->assertEmpty($errormessage, $errormessage);
 405              $this->assertSame($encodedsetting, get_config('abc_cde', 'hostiplist'));
 406              $this->assertSame($setting, $adminsetting->get_setting());
 407          }
 408  
 409          // Invalid settings.
 410          $this->assertEquals('These entries are invalid: nonvalid site name', $adminsetting->write_setting('nonvalid site name'));
 411          $this->assertEquals('Empty lines are not valid', $adminsetting->write_setting("localhost\n"));
 412      }
 413  
 414      /**
 415       * Verifies the $ADMIN global (adminroot cache) is properly reset when changing users, which might occur naturally during cron.
 416       */
 417      public function test_adminroot_cache_reset() {
 418          $this->resetAfterTest();
 419          global $DB;
 420          // Current user is a manager at site context, which won't have access to the 'debugging' section of the admin tree.
 421          $manageruser = $this->getDataGenerator()->create_user();
 422          $context = context_system::instance();
 423          $managerrole = $DB->get_record('role', array('shortname' => 'manager'));
 424          role_assign($managerrole->id, $manageruser->id, $context->id);
 425          $this->setUser($manageruser);
 426          $adminroot = admin_get_root();
 427          $section = $adminroot->locate('debugging');
 428          $this->assertEmpty($section);
 429  
 430          // Now, change the user to an admin user and confirm we get a new copy of the admin tree when next we ask for it.
 431          $adminuser = get_admin();
 432          $this->setUser($adminuser);
 433          $adminroot = admin_get_root();
 434          $section = $adminroot->locate('debugging');
 435          $this->assertInstanceOf('\admin_settingpage', $section);
 436      }
 437  }