[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/lib/ -> adminlib.php (source)

   1  <?php
   2  
   3  /**
   4   * adminlib.php - Contains functions that only administrators will ever need to use
   5   *
   6   * @author Martin Dougiamas and many others
   7   * @version  $Id: adminlib.php,v 1.153.2.54 2008/08/31 20:44:16 skodak Exp $
   8   * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
   9   * @package moodlecore
  10   */
  11  
  12  define('INSECURE_DATAROOT_WARNING', 1);
  13  define('INSECURE_DATAROOT_ERROR', 2);
  14  
  15  function upgrade_main_savepoint($result, $version) {
  16      global $CFG;
  17  
  18      if ($result) {
  19          if ($CFG->version >= $version) {
  20              // something really wrong is going on in main upgrade script
  21              error("Upgrade savepoint: Can not upgrade main version from $CFG->version to $version.");
  22          }
  23          set_config('version', $version);
  24      } else {
  25          notify ("Upgrade savepoint: Error during main upgrade to version $version");
  26      }
  27  }
  28  
  29  function upgrade_mod_savepoint($result, $version, $type) {
  30      //TODO
  31  }
  32  
  33  function upgrade_plugin_savepoint($result, $version, $type, $dir) {
  34      //TODO
  35  }
  36  
  37  function upgrade_backup_savepoint($result, $version) {
  38      //TODO
  39  }
  40  
  41  function upgrade_blocks_savepoint($result, $version, $type) {
  42      //TODO
  43  }
  44  
  45  /**
  46   * Upgrade plugins
  47   *
  48   * @uses $db
  49   * @uses $CFG
  50   * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
  51   * @param string $dir  The directory where the plugins are located (e.g. 'question/questiontypes')
  52   * @param string $return The url to prompt the user to continue to
  53   */
  54  function upgrade_plugins($type, $dir, $return) {
  55      global $CFG, $db;
  56  
  57  /// Let's know if the header has been printed, so the funcion is being called embedded in an outer page
  58      $embedded = defined('HEADER_PRINTED');
  59  
  60      $plugs = get_list_of_plugins($dir);
  61      $updated_plugins = false;
  62      $strpluginsetup  = get_string('pluginsetup');
  63  
  64      foreach ($plugs as $plug) {
  65  
  66          $fullplug = $CFG->dirroot .'/'.$dir.'/'. $plug;
  67  
  68          unset($plugin);
  69  
  70          if (is_readable($fullplug .'/version.php')) {
  71              include_once ($fullplug .'/version.php');  // defines $plugin with version etc
  72          } else {
  73              continue;                              // Nothing to do.
  74          }
  75  
  76          $oldupgrade = false;
  77          $newupgrade = false;
  78          if (is_readable($fullplug . '/db/'. $CFG->dbtype . '.php')) {
  79              include_once($fullplug . '/db/'. $CFG->dbtype . '.php');  // defines old upgrading function
  80              $oldupgrade = true;
  81          }
  82          if (is_readable($fullplug . '/db/upgrade.php')) {
  83              include_once ($fullplug . '/db/upgrade.php');  // defines new upgrading function
  84              $newupgrade = true;
  85          }
  86  
  87          if (!isset($plugin)) {
  88              continue;
  89          }
  90  
  91          if (!empty($plugin->requires)) {
  92              if ($plugin->requires > $CFG->version) {
  93                  $info = new object();
  94                  $info->pluginname = $plug;
  95                  $info->pluginversion  = $plugin->version;
  96                  $info->currentmoodle = $CFG->version;
  97                  $info->requiremoodle = $plugin->requires;
  98                  if (!$updated_plugins && !$embedded) {
  99                      print_header($strpluginsetup, $strpluginsetup,
 100                          build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
 101                          upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
 102                  }
 103                  upgrade_log_start();
 104                  notify(get_string('pluginrequirementsnotmet', 'error', $info));
 105                  $updated_plugins = true;
 106                  continue;
 107              }
 108          }
 109  
 110          $plugin->name = $plug;   // The name MUST match the directory
 111  
 112          $pluginversion = $type.'_'.$plug.'_version';
 113  
 114          if (!isset($CFG->$pluginversion)) {
 115              set_config($pluginversion, 0);
 116          }
 117  
 118          if ($CFG->$pluginversion == $plugin->version) {
 119              // do nothing
 120          } else if ($CFG->$pluginversion < $plugin->version) {
 121              if (!$updated_plugins && !$embedded) {
 122                  print_header($strpluginsetup, $strpluginsetup,
 123                          build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
 124                          upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
 125              }
 126              $updated_plugins = true;
 127              upgrade_log_start();
 128              print_heading($dir.'/'. $plugin->name .' plugin needs upgrading');
 129              $db->debug = true;
 130              @set_time_limit(0);  // To allow slow databases to complete the long SQL
 131  
 132              if ($CFG->$pluginversion == 0) {    // It's a new install of this plugin
 133              /// Both old .sql files and new install.xml are supported
 134              /// but we priorize install.xml (XMLDB) if present
 135                  $status = false;
 136                  if (file_exists($fullplug . '/db/install.xml')) {
 137                      $status = install_from_xmldb_file($fullplug . '/db/install.xml'); //New method
 138                  } else if (file_exists($fullplug .'/db/'. $CFG->dbtype .'.sql')) {
 139                      $status = modify_database($fullplug .'/db/'. $CFG->dbtype .'.sql'); //Old method
 140                  } else {
 141                      $status = true;
 142                  }
 143  
 144                  $db->debug = false;
 145              /// Continue with the instalation, roles and other stuff
 146                  if ($status) {
 147                  /// OK so far, now update the plugins record
 148                      set_config($pluginversion, $plugin->version);
 149  
 150                  /// Install capabilities
 151                      if (!update_capabilities($type.'/'.$plug)) {
 152                          error('Could not set up the capabilities for '.$plugin->name.'!');
 153                      }
 154                  /// Install events
 155                      events_update_definition($type.'/'.$plug);
 156  
 157                  /// Run local install function if there is one
 158                      if (is_readable($fullplug .'/lib.php')) {
 159                          include_once($fullplug .'/lib.php');
 160                          $installfunction = $plugin->name.'_install';
 161                          if (function_exists($installfunction)) {
 162                              if (! $installfunction() ) {
 163                                  notify('Encountered a problem running install function for '.$module->name.'!');
 164                              }
 165                          }
 166                      }
 167  
 168                      notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
 169                  } else {
 170                      notify('Installing '. $plugin->name .' FAILED!');
 171                  }
 172              } else {                            // Upgrade existing install
 173              /// Run de old and new upgrade functions for the module
 174                  $oldupgrade_function = $type.'_'.$plugin->name .'_upgrade';
 175                  $newupgrade_function = 'xmldb_' . $type.'_'.$plugin->name .'_upgrade';
 176  
 177              /// First, the old function if exists
 178                  $oldupgrade_status = true;
 179                  if ($oldupgrade && function_exists($oldupgrade_function)) {
 180                      $db->debug = true;
 181                      $oldupgrade_status = $oldupgrade_function($CFG->$pluginversion);
 182                  } else if ($oldupgrade) {
 183                      notify ('Upgrade function ' . $oldupgrade_function . ' was not available in ' .
 184                               $fullplug . '/db/' . $CFG->dbtype . '.php');
 185                  }
 186  
 187              /// Then, the new function if exists and the old one was ok
 188                  $newupgrade_status = true;
 189                  if ($newupgrade && function_exists($newupgrade_function) && $oldupgrade_status) {
 190                      $db->debug = true;
 191                      $newupgrade_status = $newupgrade_function($CFG->$pluginversion);
 192                  } else if ($newupgrade) {
 193                      notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
 194                               $fullplug . '/db/upgrade.php');
 195                  }
 196  
 197                  $db->debug=false;
 198              /// Now analyze upgrade results
 199                  if ($oldupgrade_status && $newupgrade_status) {    // No upgrading failed
 200                      // OK so far, now update the plugins record
 201                      set_config($pluginversion, $plugin->version);
 202                      if (!update_capabilities($type.'/'.$plug)) {
 203                          error('Could not update '.$plugin->name.' capabilities!');
 204                      }
 205                      events_update_definition($type.'/'.$plug);
 206                      notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
 207                  } else {
 208                      notify('Upgrading '. $plugin->name .' from '. $CFG->$pluginversion .' to '. $plugin->version .' FAILED!');
 209                  }
 210              }
 211              echo '<hr />';
 212          } else {
 213              upgrade_log_start();
 214              error('Version mismatch: '. $plugin->name .' can\'t downgrade '. $CFG->$pluginversion .' -> '. $plugin->version .' !');
 215          }
 216      }
 217  
 218      upgrade_log_finish();
 219  
 220      if ($updated_plugins && !$embedded) {
 221          print_continue($return);
 222          print_footer('none');
 223          die;
 224      }
 225  }
 226  
 227  /**
 228   * Find and check all modules and load them up or upgrade them if necessary
 229   *
 230   * @uses $db
 231   * @uses $CFG
 232   * @param string $return The url to prompt the user to continue to
 233   * @todo Finish documenting this function
 234   */
 235  function upgrade_activity_modules($return) {
 236  
 237      global $CFG, $db;
 238  
 239      if (!$mods = get_list_of_plugins('mod') ) {
 240          error('No modules installed!');
 241      }
 242  
 243      $updated_modules = false;
 244      $strmodulesetup  = get_string('modulesetup');
 245  
 246      foreach ($mods as $mod) {
 247  
 248          if ($mod == 'NEWMODULE') {   // Someone has unzipped the template, ignore it
 249              continue;
 250          }
 251  
 252          $fullmod = $CFG->dirroot .'/mod/'. $mod;
 253  
 254          unset($module);
 255  
 256          if ( is_readable($fullmod .'/version.php')) {
 257              include_once ($fullmod .'/version.php');  // defines $module with version etc
 258          } else {
 259              notify('Module '. $mod .': '. $fullmod .'/version.php was not readable');
 260              continue;
 261          }
 262  
 263          $oldupgrade = false;
 264          $newupgrade = false;
 265          if ( is_readable($fullmod .'/db/' . $CFG->dbtype . '.php')) {
 266              include_once($fullmod .'/db/' . $CFG->dbtype . '.php');  // defines old upgrading function
 267              $oldupgrade = true;
 268          }
 269          if ( is_readable($fullmod . '/db/upgrade.php')) {
 270              include_once ($fullmod . '/db/upgrade.php');  // defines new upgrading function
 271              $newupgrade = true;
 272          }
 273  
 274          if (!isset($module)) {
 275              continue;
 276          }
 277  
 278          if (!empty($module->requires)) {
 279              if ($module->requires > $CFG->version) {
 280                  $info = new object();
 281                  $info->modulename = $mod;
 282                  $info->moduleversion  = $module->version;
 283                  $info->currentmoodle = $CFG->version;
 284                  $info->requiremoodle = $module->requires;
 285                  if (!$updated_modules) {
 286                      print_header($strmodulesetup, $strmodulesetup,
 287                              build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
 288                              upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
 289                  }
 290                  upgrade_log_start();
 291                  notify(get_string('modulerequirementsnotmet', 'error', $info));
 292                  $updated_modules = true;
 293                  continue;
 294              }
 295          }
 296  
 297          $module->name = $mod;   // The name MUST match the directory
 298  
 299          include_once($fullmod.'/lib.php');  // defines upgrading and/or installing functions
 300  
 301          if ($currmodule = get_record('modules', 'name', $module->name)) {
 302              if ($currmodule->version == $module->version) {
 303                  // do nothing
 304              } else if ($currmodule->version < $module->version) {
 305              /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
 306                  if (!$oldupgrade && !$newupgrade) {
 307                      notify('Upgrade files ' . $mod . ': ' . $fullmod . '/db/' . $CFG->dbtype . '.php or ' .
 308                                                              $fullmod . '/db/upgrade.php were not readable');
 309                      continue;
 310                  }
 311                  if (!$updated_modules) {
 312                      print_header($strmodulesetup, $strmodulesetup,
 313                              build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
 314                              upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
 315                  }
 316                  upgrade_log_start();
 317                  print_heading($module->name .' module needs upgrading');
 318  
 319              /// Run de old and new upgrade functions for the module
 320                  $oldupgrade_function = $module->name . '_upgrade';
 321                  $newupgrade_function = 'xmldb_' . $module->name . '_upgrade';
 322  
 323              /// First, the old function if exists
 324                  $oldupgrade_status = true;
 325                  if ($oldupgrade && function_exists($oldupgrade_function)) {
 326                      $db->debug = true;
 327                      $oldupgrade_status = $oldupgrade_function($currmodule->version, $module);
 328                      if (!$oldupgrade_status) {
 329                          notify ('Upgrade function ' . $oldupgrade_function .
 330                                  ' did not complete successfully.');
 331                      }
 332                  } else if ($oldupgrade) {
 333                      notify ('Upgrade function ' . $oldupgrade_function . ' was not available in ' .
 334                               $mod . ': ' . $fullmod . '/db/' . $CFG->dbtype . '.php');
 335                  }
 336  
 337              /// Then, the new function if exists and the old one was ok
 338                  $newupgrade_status = true;
 339                  if ($newupgrade && function_exists($newupgrade_function) && $oldupgrade_status) {
 340                      $db->debug = true;
 341                      $newupgrade_status = $newupgrade_function($currmodule->version, $module);
 342                  } else if ($newupgrade && $oldupgrade_status) {
 343                      notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
 344                               $mod . ': ' . $fullmod . '/db/upgrade.php');
 345                  }
 346  
 347                  $db->debug=false;
 348              /// Now analyze upgrade results
 349                  if ($oldupgrade_status && $newupgrade_status) {    // No upgrading failed
 350                      // OK so far, now update the modules record
 351                      $module->id = $currmodule->id;
 352                      if (! update_record('modules', $module)) {
 353                          error('Could not update '. $module->name .' record in modules table!');
 354                      }
 355                      remove_dir($CFG->dataroot . '/cache', true); // flush cache
 356                      notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
 357                      echo '<hr />';
 358                  } else {
 359                      notify('Upgrading '. $module->name .' from '. $currmodule->version .' to '. $module->version .' FAILED!');
 360                  }
 361  
 362              /// Update the capabilities table?
 363                  if (!update_capabilities('mod/'.$module->name)) {
 364                      error('Could not update '.$module->name.' capabilities!');
 365                  }
 366                  events_update_definition('mod/'.$module->name);
 367  
 368                  $updated_modules = true;
 369  
 370              } else {
 371                  upgrade_log_start();
 372                  error('Version mismatch: '. $module->name .' can\'t downgrade '. $currmodule->version .' -> '. $module->version .' !');
 373              }
 374  
 375          } else {    // module not installed yet, so install it
 376              if (!$updated_modules) {
 377                  print_header($strmodulesetup, $strmodulesetup,
 378                          build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
 379                          upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
 380              }
 381              upgrade_log_start();
 382              print_heading($module->name);
 383              $updated_modules = true;
 384              $db->debug = true;
 385              @set_time_limit(0);  // To allow slow databases to complete the long SQL
 386  
 387          /// Both old .sql files and new install.xml are supported
 388          /// but we priorize install.xml (XMLDB) if present
 389              if (file_exists($fullmod . '/db/install.xml')) {
 390                  $status = install_from_xmldb_file($fullmod . '/db/install.xml'); //New method
 391              } else {
 392                  $status = modify_database($fullmod .'/db/'. $CFG->dbtype .'.sql'); //Old method
 393              }
 394  
 395              $db->debug = false;
 396  
 397          /// Continue with the installation, roles and other stuff
 398              if ($status) {
 399                  if ($module->id = insert_record('modules', $module)) {
 400  
 401                  /// Capabilities
 402                      if (!update_capabilities('mod/'.$module->name)) {
 403                          error('Could not set up the capabilities for '.$module->name.'!');
 404                      }
 405  
 406                  /// Events
 407                      events_update_definition('mod/'.$module->name);
 408  
 409                  /// Run local install function if there is one
 410                      $installfunction = $module->name.'_install';
 411                      if (function_exists($installfunction)) {
 412                          if (! $installfunction() ) {
 413                              notify('Encountered a problem running install function for '.$module->name.'!');
 414                          }
 415                      }
 416  
 417                      notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
 418                      echo '<hr />';
 419                  } else {
 420                      error($module->name .' module could not be added to the module list!');
 421                  }
 422              } else {
 423                  error($module->name .' tables could NOT be set up successfully!');
 424              }
 425          }
 426  
 427      /// Check submodules of this module if necessary
 428  
 429          $submoduleupgrade = $module->name.'_upgrade_submodules';
 430          if (function_exists($submoduleupgrade)) {
 431              $submoduleupgrade();
 432          }
 433  
 434  
 435      /// Run any defaults or final code that is necessary for this module
 436  
 437          if ( is_readable($fullmod .'/defaults.php')) {
 438              // Insert default values for any important configuration variables
 439              unset($defaults);
 440              include($fullmod .'/defaults.php'); // include here means execute, not library include
 441              if (!empty($defaults)) {
 442                  foreach ($defaults as $name => $value) {
 443                      if (!isset($CFG->$name)) {
 444                          set_config($name, $value);
 445                      }
 446                  }
 447              }
 448          }
 449      }
 450  
 451      upgrade_log_finish(); // finish logging if started
 452  
 453      if ($updated_modules) {
 454          print_continue($return);
 455          print_footer('none');
 456          die;
 457      }
 458  }
 459  
 460  /**
 461   * Try to obtain or release the cron lock.
 462   *
 463   * @param string  $name  name of lock
 464   * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionaly
 465   * @param bool $ignorecurrent ignore current lock state, usually entend previous lock
 466   * @return bool true if lock obtained
 467   */
 468  function set_cron_lock($name, $until, $ignorecurrent=false) {
 469      if (empty($name)) {
 470          debugging("Tried to get a cron lock for a null fieldname");
 471          return false;
 472      }
 473  
 474      // remove lock by force == remove from config table
 475      if (is_null($until)) {
 476          set_config($name, null);
 477          return true;
 478      }
 479  
 480      if (!$ignorecurrent) {
 481          // read value from db - other processes might have changed it
 482          $value = get_field('config', 'value', 'name', $name);
 483  
 484          if ($value and $value > time()) {
 485              //lock active
 486              return false;
 487          }
 488      }
 489  
 490      set_config($name, $until);
 491      return true;
 492  }
 493  
 494  function print_progress($done, $total, $updatetime=5, $sleeptime=1, $donetext='') {
 495      static $thisbarid;
 496      static $starttime;
 497      static $lasttime;
 498  
 499      if ($total < 2) {   // No need to show anything
 500          return;
 501      }
 502  
 503      // Are we done?
 504      if ($done >= $total) {
 505          $done = $total;
 506          if (!empty($thisbarid)) {
 507              $donetext .= ' ('.$done.'/'.$total.') '.get_string('success');
 508              print_progress_redraw($thisbarid, $done, $total, 500, $donetext);
 509              $thisbarid = $starttime = $lasttime = NULL;
 510          }
 511          return;
 512      }
 513  
 514      if (empty($starttime)) {
 515          $starttime = $lasttime = time();
 516          $lasttime = $starttime - $updatetime;
 517          $thisbarid = uniqid();
 518          echo '<table width="500" cellpadding="0" cellspacing="0" align="center"><tr><td width="500">';
 519          echo '<div id="bar'.$thisbarid.'" style="border-style:solid;border-width:1px;width:500px;height:50px;">';
 520          echo '<div id="slider'.$thisbarid.'" style="border-style:solid;border-width:1px;height:48px;width:10px;background-color:green;"></div>';
 521          echo '</div>';
 522          echo '<div id="text'.$thisbarid.'" align="center" style="width:500px;"></div>';
 523          echo '</td></tr></table>';
 524          echo '</div>';
 525      }
 526  
 527      $now = time();
 528  
 529      if ($done && (($now - $lasttime) >= $updatetime)) {
 530          $elapsedtime = $now - $starttime;
 531          $projectedtime = (int)(((float)$total / (float)$done) * $elapsedtime) - $elapsedtime;
 532          $percentage = round((float)$done / (float)$total, 2);
 533          $width = (int)(500 * $percentage);
 534  
 535          if ($projectedtime > 10) {
 536              $projectedtext = '  Ending: '.format_time($projectedtime);
 537          } else {
 538              $projectedtext = '';
 539          }
 540  
 541          $donetext .= ' ('.$done.'/'.$total.') '.$projectedtext;
 542          print_progress_redraw($thisbarid, $done, $total, $width, $donetext);
 543  
 544          $lasttime = $now;
 545      }
 546  }
 547  
 548  // Don't call this function directly, it's called from print_progress.
 549  function print_progress_redraw($thisbarid, $done, $total, $width, $donetext='') {
 550      if (empty($thisbarid)) {
 551          return;
 552      }
 553      echo '<script>';
 554      echo 'document.getElementById("text'.$thisbarid.'").innerHTML = "'.addslashes($donetext).'";'."\n";
 555      echo 'document.getElementById("slider'.$thisbarid.'").style.width = \''.$width.'px\';'."\n";
 556      echo '</script>';
 557  }
 558  
 559  function upgrade_get_javascript() {
 560      global $CFG;
 561  
 562      if (!empty($_SESSION['installautopilot'])) {
 563          $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = true;</script>'."\n";
 564      } else {
 565          $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = false;</script>'."\n";
 566      }
 567      $linktoscrolltoerrors .= '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>';
 568  
 569      return $linktoscrolltoerrors;
 570  }
 571  
 572  function create_admin_user() {
 573      global $CFG, $USER;
 574  
 575      if (empty($CFG->rolesactive)) {   // No admin user yet.
 576  
 577          $user = new object();
 578          $user->auth         = 'manual';
 579          $user->firstname    = get_string('admin');
 580          $user->lastname     = get_string('user');
 581          $user->username     = 'admin';
 582          $user->password     = hash_internal_user_password('admin');
 583          $user->email        = 'root@localhost';
 584          $user->confirmed    = 1;
 585          $user->mnethostid   = $CFG->mnet_localhost_id;
 586          $user->lang         = $CFG->lang;
 587          $user->maildisplay  = 1;
 588          $user->timemodified = time();
 589  
 590          if (!$user->id = insert_record('user', $user)) {
 591              error('SERIOUS ERROR: Could not create admin user record !!!');
 592          }
 593  
 594          if (!$user = get_record('user', 'id', $user->id)) {   // Double check.
 595              error('User ID was incorrect (can\'t find it)');
 596          }
 597  
 598          // Assign the default admin roles to the new user.
 599          if (!$adminroles = get_roles_with_capability('moodle/legacy:admin', CAP_ALLOW)) {
 600              error('No admin role could be found');
 601          }
 602          $sitecontext = get_context_instance(CONTEXT_SYSTEM);
 603          foreach ($adminroles as $adminrole) {
 604              role_assign($adminrole->id, $user->id, 0, $sitecontext->id);
 605          }
 606  
 607          set_config('rolesactive', 1);
 608  
 609          // Log the user in.
 610          $USER = get_complete_user_data('username', 'admin');
 611          $USER->newadminuser = 1;
 612          load_all_capabilities();
 613  
 614          redirect("$CFG->wwwroot/user/editadvanced.php?id=$user->id");  // Edit thyself
 615      } else {
 616          error('Can not create admin!');
 617      }
 618  }
 619  
 620  ////////////////////////////////////////////////
 621  /// upgrade logging functions
 622  ////////////////////////////////////////////////
 623  
 624  $upgradeloghandle = false;
 625  $upgradelogbuffer = '';
 626  // I did not find out how to use static variable in callback function,
 627  // the problem was that I could not flush the static buffer :-(
 628  global $upgradeloghandle, $upgradelogbuffer;
 629  
 630  /**
 631   * Check if upgrade is already running.
 632   *
 633   * If anything goes wrong due to missing call to upgrade_log_finish()
 634   * just restart the browser.
 635   *
 636   * @param string warning message indicating upgrade is already running
 637   * @param int page reload timeout
 638   */
 639  function upgrade_check_running($message, $timeout) {
 640      if (!empty($_SESSION['upgraderunning'])) {
 641          print_header();
 642          redirect(me(), $message, $timeout);
 643      }
 644  }
 645  
 646  /**
 647   * Start logging of output into file (if not disabled) and
 648   * prevent aborting and concurrent execution of upgrade script.
 649   *
 650   * Please note that you can not write into session variables after calling this function!
 651   *
 652   * This function may be called repeatedly.
 653   */
 654  function upgrade_log_start() {
 655      global $CFG, $upgradeloghandle;
 656  
 657      if (!empty($_SESSION['upgraderunning'])) {
 658          return; // logging already started
 659      }
 660  
 661      @ignore_user_abort(true);            // ignore if user stops or otherwise aborts page loading
 662      $_SESSION['upgraderunning'] = 1;     // set upgrade indicator
 663      if (empty($CFG->dbsessions)) {       // workaround for bug in adodb, db session can not be restarted
 664          session_write_close();           // from now on user can reload page - will be displayed warning
 665      }
 666      make_upload_directory('upgradelogs');
 667      ob_start('upgrade_log_callback', 2); // function for logging to disk; flush each line of text ASAP
 668      register_shutdown_function('upgrade_log_finish'); // in case somebody forgets to stop logging
 669  }
 670  
 671  /**
 672   * Terminate logging of output, flush all data, allow script aborting
 673   * and reopen session for writing. Function error() does terminate the logging too.
 674   *
 675   * Please make sure that each upgrade_log_start() is properly terminated by
 676   * this function or error().
 677   *
 678   * This function may be called repeatedly.
 679   */
 680  function upgrade_log_finish() {
 681      global $CFG, $upgradeloghandle, $upgradelogbuffer;
 682  
 683      if (empty($_SESSION['upgraderunning'])) {
 684          return; // logging already terminated
 685      }
 686  
 687      @ob_end_flush();
 688      if ($upgradelogbuffer !== '') {
 689          @fwrite($upgradeloghandle, $upgradelogbuffer);
 690          $upgradelogbuffer = '';
 691      }
 692      if ($upgradeloghandle and ($upgradeloghandle !== 'error')) {
 693          @fclose($upgradeloghandle);
 694          $upgradeloghandle = false;
 695      }
 696      if (empty($CFG->dbsessions)) {
 697          @session_start();                // ignore header errors, we only need to reopen session
 698      }
 699      $_SESSION['upgraderunning'] = 0; // clear upgrade indicator
 700      if (connection_aborted()) {
 701          die;
 702      }
 703      @ignore_user_abort(false);
 704  }
 705  
 706  /**
 707   * Callback function for logging into files. Not more than one file is created per minute,
 708   * upgrade session (terminated by upgrade_log_finish()) is always stored in one file.
 709   *
 710   * This function must not output any characters or throw warnigns and errors!
 711   */
 712  function upgrade_log_callback($string) {
 713      global $CFG, $upgradeloghandle, $upgradelogbuffer;
 714  
 715      if (empty($CFG->disableupgradelogging) and ($string != '') and ($upgradeloghandle !== 'error')) {
 716          if ($upgradeloghandle or ($upgradeloghandle = @fopen($CFG->dataroot.'/upgradelogs/upg_'.date('Ymd-Hi').'.html', 'a'))) {
 717              $upgradelogbuffer .= $string;
 718              if (strlen($upgradelogbuffer) > 2048) { // 2kB write buffer
 719                  @fwrite($upgradeloghandle, $upgradelogbuffer);
 720                  $upgradelogbuffer = '';
 721              }
 722          } else {
 723              $upgradeloghandle = 'error';
 724          }
 725      }
 726      return $string;
 727  }
 728  
 729  /**
 730   * Test if and critical warnings are present
 731   * @return bool
 732   */
 733  function admin_critical_warnings_present() {
 734      global $SESSION;
 735  
 736      if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
 737          return 0;
 738      }
 739  
 740      if (!isset($SESSION->admin_critical_warning)) {
 741          $SESSION->admin_critical_warning = 0;
 742          if (ini_get_bool('register_globals')) {
 743              $SESSION->admin_critical_warning = 1;
 744          } else if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
 745              $SESSION->admin_critical_warning = 1;
 746          }
 747      }
 748  
 749      return $SESSION->admin_critical_warning;
 750  }
 751  
 752  /**
 753   * Try to verify that dataroot is not accessible from web.
 754   * It is not 100% correct but might help to reduce number of vulnerable sites.
 755   *
 756   * Protection from httpd.conf and .htaccess is not detected properly.
 757   * @param bool $fetchtest try to test public access by fetching file
 758   * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING migth be problematic
 759   */
 760  function is_dataroot_insecure($fetchtest=false) {
 761      global $CFG;
 762  
 763      $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
 764  
 765      $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
 766      $rp = strrev(trim($rp, '/'));
 767      $rp = explode('/', $rp);
 768      foreach($rp as $r) {
 769          if (strpos($siteroot, '/'.$r.'/') === 0) {
 770              $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
 771          } else {
 772              break; // probably alias root
 773          }
 774      }
 775  
 776      $siteroot = strrev($siteroot);
 777      $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
 778  
 779      if (strpos($dataroot, $siteroot) !== 0) {
 780          return false;
 781      }
 782  
 783      if (!$fetchtest) {
 784          return INSECURE_DATAROOT_WARNING;
 785      }
 786  
 787      // now try all methods to fetch a test file using http protocol
 788  
 789      $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
 790      preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
 791      $httpdocroot = $matches[1];
 792      $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
 793      if (make_upload_directory('diag', false) === false) {
 794          return INSECURE_DATAROOT_WARNING;
 795      }
 796      $testfile = $CFG->dataroot.'/diag/public.txt';
 797      if (!file_exists($testfile)) {
 798          file_put_contents($testfile, 'test file, do not delete');
 799      }
 800      $teststr = trim(file_get_contents($testfile));
 801      if (empty($teststr)) {
 802          // hmm, strange
 803          return INSECURE_DATAROOT_WARNING;
 804      }
 805  
 806      $testurl = $datarooturl.'/diag/public.txt';
 807  
 808      if (extension_loaded('curl') and ($ch = @curl_init($testurl)) !== false) {
 809          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 810          curl_setopt($ch, CURLOPT_HEADER, false);
 811          $data = curl_exec($ch);
 812          if (!curl_errno($ch)) {
 813              $data = trim($data);
 814              if ($data === $teststr) {
 815                  curl_close($ch);
 816                  return INSECURE_DATAROOT_ERROR;
 817              }
 818          }
 819          curl_close($ch);
 820      }
 821  
 822      if ($data = @file_get_contents($testurl)) {
 823          $data = trim($data);
 824          if ($data === $teststr) {
 825              return INSECURE_DATAROOT_ERROR;
 826          }
 827      }
 828  
 829      preg_match('|https?://([^/]+)|i', $testurl, $matches);
 830      $sitename = $matches[1];
 831      $error = 0;
 832      if ($fp = @fsockopen($sitename, 80, $error)) {
 833          preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
 834          $localurl = $matches[1];
 835          $out = "GET $localurl HTTP/1.1\r\n";
 836          $out .= "Host: $sitename\r\n";
 837          $out .= "Connection: Close\r\n\r\n";
 838          fwrite($fp, $out);
 839          $data = '';
 840          $incoming = false;
 841          while (!feof($fp)) {
 842              if ($incoming) {
 843                  $data .= fgets($fp, 1024);
 844              } else if (@fgets($fp, 1024) === "\r\n") {
 845                  $incoming = true;
 846              }
 847          }
 848          fclose($fp);
 849          $data = trim($data);
 850          if ($data === $teststr) {
 851              return INSECURE_DATAROOT_ERROR;
 852          }
 853      }
 854  
 855      return INSECURE_DATAROOT_WARNING;
 856  }
 857  
 858  /// =============================================================================================================
 859  /// administration tree classes and functions
 860  
 861  
 862  // n.b. documentation is still in progress for this code
 863  
 864  /// INTRODUCTION
 865  
 866  /// This file performs the following tasks:
 867  ///  -it defines the necessary objects and interfaces to build the Moodle
 868  ///   admin hierarchy
 869  ///  -it defines the admin_externalpage_setup(), admin_externalpage_print_header(),
 870  ///   and admin_externalpage_print_footer() functions used on admin pages
 871  
 872  /// ADMIN_SETTING OBJECTS
 873  
 874  /// Moodle settings are represented by objects that inherit from the admin_setting
 875  /// class. These objects encapsulate how to read a setting, how to write a new value
 876  /// to a setting, and how to appropriately display the HTML to modify the setting.
 877  
 878  /// ADMIN_SETTINGPAGE OBJECTS
 879  
 880  /// The admin_setting objects are then grouped into admin_settingpages. The latter
 881  /// appear in the Moodle admin tree block. All interaction with admin_settingpage
 882  /// objects is handled by the admin/settings.php file.
 883  
 884  /// ADMIN_EXTERNALPAGE OBJECTS
 885  
 886  /// There are some settings in Moodle that are too complex to (efficiently) handle
 887  /// with admin_settingpages. (Consider, for example, user management and displaying
 888  /// lists of users.) In this case, we use the admin_externalpage object. This object
 889  /// places a link to an external PHP file in the admin tree block.
 890  
 891  /// If you're using an admin_externalpage object for some settings, you can take
 892  /// advantage of the admin_externalpage_* functions. For example, suppose you wanted
 893  /// to add a foo.php file into admin. First off, you add the following line to
 894  /// admin/settings/first.php (at the end of the file) or to some other file in
 895  /// admin/settings:
 896  
 897  ///    $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
 898  ///        $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
 899  
 900  /// Next, in foo.php, your file structure would resemble the following:
 901  
 902  ///        require_once('.../config.php');
 903  ///        require_once($CFG->libdir.'/adminlib.php');
 904  ///        admin_externalpage_setup('foo');
 905  ///        // functionality like processing form submissions goes here
 906  ///        admin_externalpage_print_header();
 907  ///        // your HTML goes here
 908  ///        admin_externalpage_print_footer();
 909  
 910  /// The admin_externalpage_setup() function call ensures the user is logged in,
 911  /// and makes sure that they have the proper role permission to access the page.
 912  
 913  /// The admin_externalpage_print_header() function prints the header (it figures
 914  /// out what category and subcategories the page is classified under) and ensures
 915  /// that you're using the admin pagelib (which provides the admin tree block and
 916  /// the admin bookmarks block).
 917  
 918  /// The admin_externalpage_print_footer() function properly closes the tables
 919  /// opened up by the admin_externalpage_print_header() function and prints the
 920  /// standard Moodle footer.
 921  
 922  /// ADMIN_CATEGORY OBJECTS
 923  
 924  /// Above and beyond all this, we have admin_category objects. These objects
 925  /// appear as folders in the admin tree block. They contain admin_settingpage's,
 926  /// admin_externalpage's, and other admin_category's.
 927  
 928  /// OTHER NOTES
 929  
 930  /// admin_settingpage's, admin_externalpage's, and admin_category's all inherit
 931  /// from part_of_admin_tree (a pseudointerface). This interface insists that
 932  /// a class has a check_access method for access permissions, a locate method
 933  /// used to find a specific node in the admin tree and find parent path.
 934  
 935  /// admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
 936  /// interface ensures that the class implements a recursive add function which
 937  /// accepts a part_of_admin_tree object and searches for the proper place to
 938  /// put it. parentable_part_of_admin_tree implies part_of_admin_tree.
 939  
 940  /// Please note that the $this->name field of any part_of_admin_tree must be
 941  /// UNIQUE throughout the ENTIRE admin tree.
 942  
 943  /// The $this->name field of an admin_setting object (which is *not* part_of_
 944  /// admin_tree) must be unique on the respective admin_settingpage where it is
 945  /// used.
 946  
 947  
 948  /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
 949  
 950  /**
 951   * Pseudointerface for anything appearing in the admin tree
 952   *
 953   * The pseudointerface that is implemented by anything that appears in the admin tree
 954   * block. It forces inheriting classes to define a method for checking user permissions
 955   * and methods for finding something in the admin tree.
 956   *
 957   * @author Vincenzo K. Marcovecchio
 958   * @package admin
 959   */
 960  class part_of_admin_tree {
 961  
 962      /**
 963       * Finds a named part_of_admin_tree.
 964       *
 965       * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
 966       * and not parentable_part_of_admin_tree, then this function should only check if
 967       * $this->name matches $name. If it does, it should return a reference to $this,
 968       * otherwise, it should return a reference to NULL.
 969       *
 970       * If a class inherits parentable_part_of_admin_tree, this method should be called
 971       * recursively on all child objects (assuming, of course, the parent object's name
 972       * doesn't match the search criterion).
 973       *
 974       * @param string $name The internal name of the part_of_admin_tree we're searching for.
 975       * @return mixed An object reference or a NULL reference.
 976       */
 977      function &locate($name) {
 978          trigger_error('Admin class does not implement method <strong>locate()</strong>', E_USER_WARNING);
 979          return;
 980      }
 981  
 982      /**
 983       * Removes named part_of_admin_tree.
 984       *
 985       * @param string $name The internal name of the part_of_admin_tree we want to remove.
 986       * @return bool success.
 987       */
 988      function prune($name) {
 989          trigger_error('Admin class does not implement method <strong>prune()</strong>', E_USER_WARNING);
 990          return;
 991      }
 992  
 993      /**
 994       * Search using query
 995       * @param strin query
 996       * @return mixed array-object structure of found settings and pages
 997       */
 998      function search($query) {
 999          trigger_error('Admin class does not implement method <strong>search()</strong>', E_USER_WARNING);
1000          return;
1001      }
1002  
1003      /**
1004       * Verifies current user's access to this part_of_admin_tree.
1005       *
1006       * Used to check if the current user has access to this part of the admin tree or
1007       * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
1008       * then this method is usually just a call to has_capability() in the site context.
1009       *
1010       * If a class inherits parentable_part_of_admin_tree, this method should return the
1011       * logical OR of the return of check_access() on all child objects.
1012       *
1013       * @return bool True if the user has access, false if she doesn't.
1014       */
1015      function check_access() {
1016          trigger_error('Admin class does not implement method <strong>check_access()</strong>', E_USER_WARNING);
1017          return;
1018      }
1019  
1020      /**
1021       * Mostly usefull for removing of some parts of the tree in admin tree block.
1022       *
1023       * @return True is hidden from normal list view
1024       */
1025      function is_hidden() {
1026          trigger_error('Admin class does not implement method <strong>is_hidden()</strong>', E_USER_WARNING);
1027          return;
1028      }
1029  }
1030  
1031  /**
1032   * Pseudointerface implemented by any part_of_admin_tree that has children.
1033   *
1034   * The pseudointerface implemented by any part_of_admin_tree that can be a parent
1035   * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
1036   * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
1037   * include an add method for adding other part_of_admin_tree objects as children.
1038   *
1039   * @author Vincenzo K. Marcovecchio
1040   * @package admin
1041   */
1042  class parentable_part_of_admin_tree extends part_of_admin_tree {
1043  
1044      /**
1045       * Adds a part_of_admin_tree object to the admin tree.
1046       *
1047       * Used to add a part_of_admin_tree object to this object or a child of this
1048       * object. $something should only be added if $destinationname matches
1049       * $this->name. If it doesn't, add should be called on child objects that are
1050       * also parentable_part_of_admin_tree's.
1051       *
1052       * @param string $destinationname The internal name of the new parent for $something.
1053       * @param part_of_admin_tree &$something The object to be added.
1054       * @return bool True on success, false on failure.
1055       */
1056      function add($destinationname, $something) {
1057          trigger_error('Admin class does not implement method <strong>add()</strong>', E_USER_WARNING);
1058          return;
1059      }
1060  
1061  }
1062  
1063  /**
1064   * The object used to represent folders (a.k.a. categories) in the admin tree block.
1065   *
1066   * Each admin_category object contains a number of part_of_admin_tree objects.
1067   *
1068   * @author Vincenzo K. Marcovecchio
1069   * @package admin
1070   */
1071  class admin_category extends parentable_part_of_admin_tree {
1072  
1073      /**
1074       * @var mixed An array of part_of_admin_tree objects that are this object's children
1075       */
1076      var $children;
1077  
1078      /**
1079       * @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1080       */
1081      var $name;
1082  
1083      /**
1084       * @var string The displayed name for this category. Usually obtained through get_string()
1085       */
1086      var $visiblename;
1087  
1088      /**
1089       * @var bool Should this category be hidden in admin tree block?
1090       */
1091      var $hidden;
1092  
1093      /**
1094       * paths
1095       */
1096      var $path;
1097      var $visiblepath;
1098  
1099      /**
1100       * Constructor for an empty admin category
1101       *
1102       * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1103       * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
1104       * @param bool $hidden hide category in admin tree block
1105       */
1106      function admin_category($name, $visiblename, $hidden=false) {
1107          $this->children    = array();
1108          $this->name        = $name;
1109          $this->visiblename = $visiblename;
1110          $this->hidden      = $hidden;
1111      }
1112  
1113      /**
1114       * Returns a reference to the part_of_admin_tree object with internal name $name.
1115       *
1116       * @param string $name The internal name of the object we want.
1117       * @param bool $findpath initialize path and visiblepath arrays
1118       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1119       */
1120      function &locate($name, $findpath=false) {
1121          if ($this->name == $name) {
1122              if ($findpath) {
1123                  $this->visiblepath[] = $this->visiblename;
1124                  $this->path[]        = $this->name;
1125              }
1126              return $this;
1127          }
1128  
1129          $return = NULL;
1130          foreach($this->children as $childid=>$unused) {
1131              if ($return =& $this->children[$childid]->locate($name, $findpath)) {
1132                  break;
1133              }
1134          }
1135  
1136          if (!is_null($return) and $findpath) {
1137              $return->visiblepath[] = $this->visiblename;
1138              $return->path[]        = $this->name;
1139          }
1140  
1141          return $return;
1142      }
1143  
1144      /**
1145       * Search using query
1146       * @param strin query
1147       * @return mixed array-object structure of found settings and pages
1148       */
1149      function search($query) {
1150          $result = array();
1151          foreach ($this->children as $child) {
1152              $subsearch = $child->search($query);
1153              if (!is_array($subsearch)) {
1154                  debugging('Incorrect search result from '.$child->name);
1155                  continue;
1156              }
1157              $result = array_merge($result, $subsearch);
1158          }
1159          return $result;
1160      }
1161  
1162      /**
1163       * Removes part_of_admin_tree object with internal name $name.
1164       *
1165       * @param string $name The internal name of the object we want to remove.
1166       * @return bool success
1167       */
1168      function prune($name) {
1169  
1170          if ($this->name == $name) {
1171              return false;  //can not remove itself
1172          }
1173  
1174          foreach($this->children as $precedence => $child) {
1175              if ($child->name == $name) {
1176                  // found it!
1177                  unset($this->children[$precedence]);
1178                  return true;
1179              }
1180              if ($this->children[$precedence]->prune($name)) {
1181                  return true;
1182              }
1183          }
1184          return false;
1185      }
1186  
1187      /**
1188       * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
1189       *
1190       * @param string $destinationame The internal name of the immediate parent that we want for $something.
1191       * @param mixed $something A part_of_admin_tree or setting instanceto be added.
1192       * @return bool True if successfully added, false if $something can not be added.
1193       */
1194      function add($parentname, $something) {
1195          $parent =& $this->locate($parentname);
1196          if (is_null($parent)) {
1197              debugging('parent does not exist!');
1198              return false;
1199          }
1200  
1201          if (is_a($something, 'part_of_admin_tree')) {
1202              if (!is_a($parent, 'parentable_part_of_admin_tree')) {
1203                  debugging('error - parts of tree can be inserted only into parentable parts');
1204                  return false;
1205              }
1206              $parent->children[] = $something;
1207              return true;
1208  
1209          } else {
1210              debugging('error - can not add this element');
1211              return false;
1212          }
1213  
1214      }
1215  
1216      /**
1217       * Checks if the user has access to anything in this category.
1218       *
1219       * @return bool True if the user has access to atleast one child in this category, false otherwise.
1220       */
1221      function check_access() {
1222          foreach ($this->children as $child) {
1223              if ($child->check_access()) {
1224                  return true;
1225              }
1226          }
1227          return false;
1228      }
1229  
1230      /**
1231       * Is this category hidden in admin tree block?
1232       *
1233       * @return bool True if hidden
1234       */
1235      function is_hidden() {
1236          return $this->hidden;
1237      }
1238  }
1239  
1240  class admin_root extends admin_category {
1241      /**
1242       * list of errors
1243       */
1244      var $errors;
1245  
1246      /**
1247       * search query
1248       */
1249      var $search;
1250  
1251      /**
1252       * full tree flag - true means all settings required, false onlypages required
1253       */
1254      var $fulltree;
1255  
1256  
1257      function admin_root() {
1258          parent::admin_category('root', get_string('administration'), false);
1259          $this->errors   = array();
1260          $this->search   = '';
1261          $this->fulltree = true;
1262      }
1263  }
1264  
1265  /**
1266   * Links external PHP pages into the admin tree.
1267   *
1268   * See detailed usage example at the top of this document (adminlib.php)
1269   *
1270   * @author Vincenzo K. Marcovecchio
1271   * @package admin
1272   */
1273  class admin_externalpage extends part_of_admin_tree {
1274  
1275      /**
1276       * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
1277       */
1278      var $name;
1279  
1280      /**
1281       * @var string The displayed name for this external page. Usually obtained through get_string().
1282       */
1283      var $visiblename;
1284  
1285      /**
1286       * @var string The external URL that we should link to when someone requests this external page.
1287       */
1288      var $url;
1289  
1290      /**
1291       * @var string The role capability/permission a user must have to access this external page.
1292       */
1293      var $req_capability;
1294  
1295      /**
1296       * @var object The context in which capability/permission should be checked, default is site context.
1297       */
1298      var $context;
1299  
1300      /**
1301       * @var bool hidden in admin tree block.
1302       */
1303      var $hidden;
1304  
1305      /**
1306       * visible path
1307       */
1308      var $path;
1309      var $visiblepath;
1310  
1311      /**
1312       * Constructor for adding an external page into the admin tree.
1313       *
1314       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1315       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1316       * @param string $url The external URL that we should link to when someone requests this external page.
1317       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1318       */
1319      function admin_externalpage($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1320          $this->name        = $name;
1321          $this->visiblename = $visiblename;
1322          $this->url         = $url;
1323          if (is_array($req_capability)) {
1324              $this->req_capability = $req_capability;
1325          } else {
1326              $this->req_capability = array($req_capability);
1327          }
1328          $this->hidden  = $hidden;
1329          $this->context = $context;
1330      }
1331  
1332      /**
1333       * Returns a reference to the part_of_admin_tree object with internal name $name.
1334       *
1335       * @param string $name The internal name of the object we want.
1336       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1337       */
1338      function &locate($name, $findpath=false) {
1339          if ($this->name == $name) {
1340              if ($findpath) {
1341                  $this->visiblepath = array($this->visiblename);
1342                  $this->path        = array($this->name);
1343              }
1344              return $this;
1345          } else {
1346              $return = NULL;
1347              return $return;
1348          }
1349      }
1350  
1351      function prune($name) {
1352          return false;
1353      }
1354  
1355      /**
1356       * Search using query
1357       * @param strin query
1358       * @return mixed array-object structure of found settings and pages
1359       */
1360      function search($query) {
1361          $textlib = textlib_get_instance();
1362  
1363          $found = false;
1364          if (strpos(strtolower($this->name), $query) !== false) {
1365              $found = true;
1366          } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1367              $found = true;
1368          }
1369          if ($found) {
1370              $result = new object();
1371              $result->page     = $this;
1372              $result->settings = array();
1373              return array($this->name => $result);
1374          } else {
1375              return array();
1376          }
1377      }
1378  
1379      /**
1380       * Determines if the current user has access to this external page based on $this->req_capability.
1381       * @return bool True if user has access, false otherwise.
1382       */
1383      function check_access() {
1384          if (!get_site()) {
1385              return true; // no access check before site is fully set up
1386          }
1387          $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
1388          foreach($this->req_capability as $cap) {
1389              if (has_capability($cap, $context)) {
1390                  return true;
1391              }
1392          }
1393          return false;
1394      }
1395  
1396      /**
1397       * Is this external page hidden in admin tree block?
1398       *
1399       * @return bool True if hidden
1400       */
1401      function is_hidden() {
1402          return $this->hidden;
1403      }
1404  
1405  }
1406  
1407  /**
1408   * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1409   *
1410   * @author Vincenzo K. Marcovecchio
1411   * @package admin
1412   */
1413  class admin_settingpage extends part_of_admin_tree {
1414  
1415      /**
1416       * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
1417       */
1418      var $name;
1419  
1420      /**
1421       * @var string The displayed name for this external page. Usually obtained through get_string().
1422       */
1423      var $visiblename;
1424      /**
1425       * @var mixed An array of admin_setting objects that are part of this setting page.
1426       */
1427      var $settings;
1428  
1429      /**
1430       * @var string The role capability/permission a user must have to access this external page.
1431       */
1432      var $req_capability;
1433  
1434      /**
1435       * @var object The context in which capability/permission should be checked, default is site context.
1436       */
1437      var $context;
1438  
1439      /**
1440       * @var bool hidden in admin tree block.
1441       */
1442      var $hidden;
1443  
1444      /**
1445       * paths
1446       */
1447      var $path;
1448      var $visiblepath;
1449  
1450      // see admin_externalpage
1451      function admin_settingpage($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1452          $this->settings    = new object();
1453          $this->name        = $name;
1454          $this->visiblename = $visiblename;
1455          if (is_array($req_capability)) {
1456              $this->req_capability = $req_capability;
1457          } else {
1458              $this->req_capability = array($req_capability);
1459          }
1460          $this->hidden      = $hidden;
1461          $this->context     = $context;
1462      }
1463  
1464      // see admin_category
1465      function &locate($name, $findpath=false) {
1466          if ($this->name == $name) {
1467              if ($findpath) {
1468                  $this->visiblepath = array($this->visiblename);
1469                  $this->path        = array($this->name);
1470              }
1471              return $this;
1472          } else {
1473              $return = NULL;
1474              return $return;
1475          }
1476      }
1477  
1478      function search($query) {
1479          $found = array();
1480  
1481          foreach ($this->settings as $setting) {
1482              if ($setting->is_related($query)) {
1483                  $found[] = $setting;
1484              }
1485          }
1486  
1487          if ($found) {
1488              $result = new object();
1489              $result->page     = $this;
1490              $result->settings = $found;
1491              return array($this->name => $result);
1492          }
1493  
1494          $textlib = textlib_get_instance();
1495  
1496          $found = false;
1497          if (strpos(strtolower($this->name), $query) !== false) {
1498              $found = true;
1499          } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1500              $found = true;
1501          }
1502          if ($found) {
1503              $result = new object();
1504              $result->page     = $this;
1505              $result->settings = array();
1506              return array($this->name => $result);
1507          } else {
1508              return array();
1509          }
1510      }
1511  
1512      function prune($name) {
1513          return false;
1514      }
1515  
1516      /**
1517       * not the same as add for admin_category. adds an admin_setting to this admin_settingpage. settings appear (on the settingpage) in the order in which they're added
1518       * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1519       * @param object $setting is the admin_setting object you want to add
1520       * @return true if successful, false if not
1521       */
1522      function add($setting) {
1523          if (!is_a($setting, 'admin_setting')) {
1524              debugging('error - not a setting instance');
1525              return false;
1526          }
1527  
1528          $this->settings->{$setting->name} = $setting;
1529          return true;
1530      }
1531  
1532      // see admin_externalpage
1533      function check_access() {
1534          if (!get_site()) {
1535              return true; // no access check before site is fully set up
1536          }
1537          $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
1538          foreach($this->req_capability as $cap) {
1539              if (has_capability($cap, $context)) {
1540                  return true;
1541              }
1542          }
1543          return false;
1544      }
1545  
1546      /**
1547       * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1548       * returns a string of the html
1549       */
1550      function output_html() {
1551          $adminroot =& admin_get_root();
1552          $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1553          foreach($this->settings as $setting) {
1554              $fullname = $setting->get_full_name();
1555              if (array_key_exists($fullname, $adminroot->errors)) {
1556                  $data = $adminroot->errors[$fullname]->data;
1557              } else {
1558                  $data = $setting->get_setting();
1559                  if (is_null($data)) {
1560                      $data = $setting->get_defaultsetting();
1561                  }
1562              }
1563              $return .= $setting->output_html($data);
1564          }
1565          $return .= '</fieldset>';
1566          return $return;
1567      }
1568  
1569      /**
1570       * Is this settigns page hidden in admin tree block?
1571       *
1572       * @return bool True if hidden
1573       */
1574      function is_hidden() {
1575          return $this->hidden;
1576      }
1577  
1578  }
1579  
1580  
1581  /**
1582   * Admin settings class. Only exists on setting pages.
1583   * Read & write happens at this level; no authentication.
1584   */
1585  class admin_setting {
1586  
1587      var $name;
1588      var $visiblename;
1589      var $description;
1590      var $defaultsetting;
1591      var $updatedcallback;
1592      var $plugin; // null means main config table
1593  
1594      /**
1595       * Constructor
1596       * @param $name string unique ascii name
1597       * @param $visiblename string localised name
1598       * @param strin $description localised long description
1599       * @param mixed $defaultsetting string or array depending on implementation
1600       */
1601      function admin_setting($name, $visiblename, $description, $defaultsetting) {
1602          $this->name           = $name;
1603          $this->visiblename    = $visiblename;
1604          $this->description    = $description;
1605          $this->defaultsetting = $defaultsetting;
1606      }
1607  
1608      function get_full_name() {
1609          return 's_'.$this->plugin.'_'.$this->name;
1610      }
1611  
1612      function get_id() {
1613          return 'id_s_'.$this->plugin.'_'.$this->name;
1614      }
1615  
1616      function config_read($name) {
1617          global $CFG;
1618          if ($this->plugin === 'backup') {
1619              require_once($CFG->dirroot.'/backup/lib.php');
1620              $backupconfig = backup_get_config();
1621              if (isset($backupconfig->$name)) {
1622                  return $backupconfig->$name;
1623              } else {
1624                  return NULL;
1625              }
1626  
1627          } else if (!empty($this->plugin)) {
1628              $value = get_config($this->plugin, $name);
1629              return $value === false ? NULL : $value;
1630  
1631          } else {
1632              if (isset($CFG->$name)) {
1633                  return $CFG->$name;
1634              } else {
1635                  return NULL;
1636              }
1637          }
1638      }
1639  
1640      function config_write($name, $value) {
1641          global $CFG;
1642          if ($this->plugin === 'backup') {
1643              require_once($CFG->dirroot.'/backup/lib.php');
1644              return (boolean)backup_set_config($name, $value);
1645          } else {
1646              return (boolean)set_config($name, $value, $this->plugin);
1647          }
1648      }
1649  
1650      /**
1651       * Returns current value of this setting
1652       * @return mixed array or string depending on instance, NULL means not set yet
1653       */
1654      function get_setting() {
1655          // has to be overridden
1656          return NULL;
1657      }
1658  
1659      /**
1660       * Returns default setting if exists
1661       * @return mixed array or string depending on instance; NULL means no default, user must supply
1662       */
1663      function get_defaultsetting() {
1664          return $this->defaultsetting;
1665      }
1666  
1667      /**
1668       * Store new setting
1669       * @param mixed string or array, must not be NULL
1670       * @return '' if ok, string error message otherwise
1671       */
1672      function write_setting($data) {
1673          // should be overridden
1674          return '';
1675      }
1676  
1677      /**
1678       * Return part of form with setting
1679       * @param mixed data array or string depending on setting
1680       * @return string
1681       */
1682      function output_html($data, $query='') {
1683          // should be overridden
1684          return;
1685      }
1686  
1687      /**
1688       * function called if setting updated - cleanup, cache reset, etc.
1689       */
1690      function set_updatedcallback($functionname) {
1691          $this->updatedcallback = $functionname;
1692      }
1693  
1694      /**
1695       * Is setting related to query text - used when searching
1696       * @param string $query
1697       * @return bool
1698       */
1699      function is_related($query) {
1700          if (strpos(strtolower($this->name), $query) !== false) {
1701              return true;
1702          }
1703          $textlib = textlib_get_instance();
1704          if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1705              return true;
1706          }
1707          if (strpos($textlib->strtolower($this->description), $query) !== false) {
1708              return true;
1709          }
1710          $current = $this->get_setting();
1711          if (!is_null($current)) {
1712              if (is_string($current)) {
1713                  if (strpos($textlib->strtolower($current), $query) !== false) {
1714                      return true;
1715                  }
1716              }
1717          }
1718          $default = $this->get_defaultsetting();
1719          if (!is_null($default)) {
1720              if (is_string($default)) {
1721                  if (strpos($textlib->strtolower($default), $query) !== false) {
1722                      return true;
1723                  }
1724              }
1725          }
1726          return false;
1727      }
1728  }
1729  
1730  /**
1731   * No setting - just heading and text.
1732   */
1733  class admin_setting_heading extends admin_setting {
1734      /**
1735       * not a setting, just text
1736       * @param string $name of setting
1737       * @param string $heading heading
1738       * @param string $information text in box
1739       */
1740      function admin_setting_heading($name, $heading, $information) {
1741          parent::admin_setting($name, $heading, $information, '');
1742      }
1743  
1744      function get_setting() {
1745          return true;
1746      }
1747  
1748      function get_defaultsetting() {
1749          return true;
1750      }
1751  
1752      function write_setting($data) {
1753          // do not write any setting
1754          return '';
1755      }
1756  
1757      function output_html($data, $query='') {
1758          $return = '';
1759          if ($this->visiblename != '') {
1760              $return .= print_heading('<a name="'.$this->name.'">'.highlightfast($query, $this->visiblename).'</a>', '', 3, 'main', true);
1761          }
1762          if ($this->description != '') {
1763              $return .= print_box(highlight($query, $this->description), 'generalbox formsettingheading', '', true);
1764          }
1765          return $return;
1766      }
1767  }
1768  
1769  /**
1770   * The most flexibly setting, user is typing text
1771   */
1772  class admin_setting_configtext extends admin_setting {
1773  
1774      var $paramtype;
1775      var $size;
1776  
1777      /**
1778       * config text contructor
1779       * @param string $name of setting
1780       * @param string $visiblename localised
1781       * @param string $description long localised info
1782       * @param string $defaultsetting
1783       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
1784       * @param int $size default field size
1785       */
1786      function admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
1787          $this->paramtype = $paramtype;
1788          if (!is_null($size)) {
1789              $this->size  = $size;
1790          } else {
1791              $this->size  = ($paramtype == PARAM_INT) ? 5 : 30;
1792          }
1793          parent::admin_setting($name, $visiblename, $description, $defaultsetting);
1794      }
1795  
1796      function get_setting() {
1797          return $this->config_read($this->name);
1798      }
1799  
1800      function write_setting($data) {
1801          if ($this->paramtype === PARAM_INT and $data === '') {
1802              // do not complain if '' used instead of 0
1803              $data = 0;
1804          }
1805          // $data is a string
1806          $validated = $this->validate($data); 
1807          if ($validated !== true) {
1808              return $validated;
1809          }
1810          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1811      }
1812  
1813      /**
1814       * Validate data before storage
1815       * @param string data
1816       * @return mixed true if ok string if error found
1817       */
1818      function validate($data) {
1819          if (is_string($this->paramtype)) {
1820              if (preg_match($this->paramtype, $data)) {
1821                  return true;
1822              } else {
1823                  return get_string('validateerror', 'admin');
1824              }
1825  
1826          } else if ($this->paramtype === PARAM_RAW) {
1827              return true;
1828  
1829          } else {
1830              $cleaned = stripslashes(clean_param(addslashes($data), $this->paramtype));
1831              if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
1832                  return true;
1833              } else {
1834                  return get_string('validateerror', 'admin');
1835              }
1836          }
1837      }
1838  
1839      function output_html($data, $query='') {
1840          $default = $this->get_defaultsetting();
1841  
1842          return format_admin_setting($this, $this->visiblename,
1843                  '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
1844                  $this->description, true, '', $default, $query);
1845      }
1846  }
1847  
1848  /**
1849   * General text area without html editor.
1850   */
1851  class admin_setting_configtextarea extends admin_setting_configtext {
1852      var $rows;
1853      var $cols;
1854  
1855      function admin_setting_configtextarea($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1856          $this->rows = $rows;
1857          $this->cols = $cols;
1858          parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype);
1859      }
1860  
1861      function output_html($data, $query='') {
1862          $default = $this->get_defaultsetting();
1863  
1864          $defaultinfo = $default;
1865          if (!is_null($default) and $default !== '') {
1866              $defaultinfo = "\n".$default;
1867          } 
1868  
1869          return format_admin_setting($this, $this->visiblename,
1870                  '<div class="form-textarea" ><textarea rows="'.$this->rows.'" cols="'.$this->cols.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'">'.s($data).'</textarea></div>',
1871                  $this->description, true, '', $defaultinfo, $query);
1872      }
1873  }
1874  
1875  /**
1876   * Password field, allows unmasking of password
1877   */
1878  class admin_setting_configpasswordunmask extends admin_setting_configtext {
1879      /**
1880       * Constructor
1881       * @param string $name of setting
1882       * @param string $visiblename localised
1883       * @param string $description long localised info
1884       * @param string $defaultsetting default password
1885       */
1886      function admin_setting_configpasswordunmask($name, $visiblename, $description, $defaultsetting) {
1887          parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
1888      }
1889  
1890      function output_html($data, $query='') {
1891          $id = $this->get_id();
1892          $unmask = get_string('unmaskpassword', 'form');
1893          $unmaskjs = '<script type="text/javascript">
1894  //<![CDATA[
1895  document.write(\'<span class="unmask"><input id="'.$id.'unmask" value="1" type="checkbox" onclick="unmaskPassword(\\\''.$id.'\\\')"/><label for="'.$id.'unmask">'.addslashes_js($unmask).'<\/label><\/span>\');
1896  document.getElementById("'.$this->get_id().'").setAttribute("autocomplete", "off");
1897  //]]>
1898  </script>';
1899          return format_admin_setting($this, $this->visiblename,
1900                  '<div class="form-password"><input type="password" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$unmaskjs.'</div>',
1901                  $this->description, true, '', NULL, $query);
1902      }
1903  }
1904  
1905  /**
1906   * Path to directory
1907   */
1908  class admin_setting_configfile extends admin_setting_configtext {
1909      /**
1910       * Constructor
1911       * @param string $name of setting
1912       * @param string $visiblename localised
1913       * @param string $description long localised info
1914       * @param string $defaultdirectory default directory location
1915       */
1916      function admin_setting_configfile($name, $visiblename, $description, $defaultdirectory) {
1917          parent::admin_setting_configtext($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
1918      }
1919  
1920      function output_html($data, $query='') {
1921          $default = $this->get_defaultsetting();
1922  
1923          if ($data) {
1924              if (file_exists($data)) {
1925                  $executable = '<span class="pathok">&#x2714;</span>';
1926              } else {
1927                  $executable = '<span class="patherror">&#x2718;</span>';
1928              }
1929          } else {
1930              $executable = '';
1931          }
1932  
1933          return format_admin_setting($this, $this->visiblename,
1934                  '<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
1935                  $this->description, true, '', $default, $query);
1936      }
1937  }
1938  
1939  /**
1940   * Path to executable file
1941   */
1942  class admin_setting_configexecutable extends admin_setting_configfile {
1943  
1944      function output_html($data, $query='') {
1945          $default = $this->get_defaultsetting();
1946  
1947          if ($data) {
1948              if (file_exists($data) and is_executable($data)) {
1949                  $executable = '<span class="pathok">&#x2714;</span>';
1950              } else {
1951                  $executable = '<span class="patherror">&#x2718;</span>';
1952              }
1953          } else {
1954              $executable = '';
1955          }
1956  
1957          return format_admin_setting($this, $this->visiblename,
1958                  '<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
1959                  $this->description, true, '', $default, $query);
1960      }
1961  }
1962  
1963  /**
1964   * Path to directory
1965   */
1966  class admin_setting_configdirectory extends admin_setting_configfile {
1967      function output_html($data, $query='') {
1968          $default = $this->get_defaultsetting();
1969  
1970          if ($data) {
1971              if (file_exists($data) and is_dir($data)) {
1972                  $executable = '<span class="pathok">&#x2714;</span>';
1973              } else {
1974                  $executable = '<span class="patherror">&#x2718;</span>';
1975              }
1976          } else {
1977              $executable = '';
1978          }
1979  
1980          return format_admin_setting($this, $this->visiblename,
1981                  '<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
1982                  $this->description, true, '', $default, $query);
1983      }
1984  }
1985  
1986  /**
1987   * Checkbox
1988   */
1989  class admin_setting_configcheckbox extends admin_setting {
1990      var $yes;
1991      var $no;
1992  
1993      /**
1994       * Constructor
1995       * @param string $name of setting
1996       * @param string $visiblename localised
1997       * @param string $description long localised info
1998       * @param string $defaultsetting
1999       * @param string $yes value used when checked
2000       * @param string $no value used when not checked
2001       */
2002      function admin_setting_configcheckbox($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2003          parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2004          $this->yes = (string)$yes;
2005          $this->no  = (string)$no;
2006      }
2007  
2008      function get_setting() {
2009          return $this->config_read($this->name);
2010      }
2011  
2012      function write_setting($data) {
2013          if ((string)$data === $this->yes) { // convert to strings before comparison
2014              $data = $this->yes;
2015          } else {
2016              $data = $this->no;
2017          }
2018          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2019      }
2020  
2021      function output_html($data, $query='') {
2022          $default = $this->get_defaultsetting();
2023  
2024          if (!is_null($default)) {
2025              if ((string)$default === $this->yes) {
2026                  $defaultinfo = get_string('checkboxyes', 'admin');
2027              } else {
2028                  $defaultinfo = get_string('checkboxno', 'admin');
2029              }
2030          } else {
2031              $defaultinfo = NULL;
2032          }
2033  
2034          if ((string)$data === $this->yes) { // convert to strings before comparison
2035              $checked = 'checked="checked"';
2036          } else {
2037              $checked = '';
2038          }
2039  
2040          return format_admin_setting($this, $this->visiblename,
2041                  '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2042                  .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2043                  $this->description, true, '', $defaultinfo, $query);
2044      }
2045  }
2046  
2047  /**
2048   * Multiple checkboxes, each represents different value, stored in csv format
2049   */
2050  class admin_setting_configmulticheckbox extends admin_setting {
2051      var $choices;
2052  
2053      /**
2054       * Constructor
2055       * @param string $name of setting
2056       * @param string $visiblename localised
2057       * @param string $description long localised info
2058       * @param array $defaultsetting array of selected
2059       * @param array $choices array of $value=>$label for each checkbox
2060       */
2061      function admin_setting_configmulticheckbox($name, $visiblename, $description, $defaultsetting, $choices) {
2062          $this->choices = $choices;
2063          parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2064      }
2065  
2066      /**
2067       * This function may be used in ancestors for lazy loading of choices
2068       * @return true if loaded, false if error
2069       */
2070      function load_choices() {
2071          /*
2072          if (is_array($this->choices)) {
2073              return true;
2074          }
2075          .... load choices here
2076          */
2077          return true;
2078      }
2079  
2080      /**
2081       * Is setting related to query text - used when searching
2082       * @param string $query
2083       * @return bool
2084       */
2085      function is_related($query) {
2086          if (!$this->load_choices() or empty($this->choices)) {
2087              return false;
2088          }
2089          if (parent::is_related($query)) {
2090              return true;
2091          }
2092  
2093          $textlib = textlib_get_instance();
2094          foreach ($this->choices as $desc) {
2095              if (strpos($textlib->strtolower($desc), $query) !== false) {
2096                  return true;
2097              }
2098          }
2099          return false;
2100      }
2101  
2102      function get_setting() {
2103          $result = $this->config_read($this->name);
2104          if (is_null($result)) {
2105              return NULL;
2106          }
2107          if ($result === '') {
2108              return array();
2109          }
2110          return explode(',', $result);
2111      }
2112  
2113      function write_setting($data) {
2114          if (!is_array($data)) {
2115              return ''; // ignore it
2116          }
2117          if (!$this->load_choices() or empty($this->choices)) {
2118              return '';
2119          }
2120          unset($data['xxxxx']);
2121          $result = array();
2122          foreach ($data as $key => $value) {
2123              if ($value and array_key_exists($key, $this->choices)) {
2124                  $result[] = $key;
2125              }
2126          }
2127          return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2128      }
2129  
2130      function output_html($data, $query='') {
2131          if (!$this->load_choices() or empty($this->choices)) {
2132              return '';
2133          }
2134          $default = $this->get_defaultsetting();
2135          if (is_null($default)) {
2136              $default = array();
2137          }
2138          if (is_null($data)) {
2139              foreach ($default as $key=>$value) {
2140                  if ($value) {
2141                      $current[] = $value;
2142                  }
2143              }
2144          }
2145  
2146          $options = array();
2147          $defaults = array();
2148          foreach($this->choices as $key=>$description) {
2149              if (in_array($key, $data)) {
2150                  $checked = 'checked="checked"';
2151              } else {
2152                  $checked = '';
2153              }
2154              if (!empty($default[$key])) {
2155                  $defaults[] = $description;
2156              }
2157  
2158              $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
2159                           .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
2160          }
2161  
2162          if (is_null(