Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • /lib/ -> adminlib.php (source)

    Differences Between: [Versions 28 and 29] [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       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   * Functions and classes used during installation, upgrades and for admin settings.
      19   *
      20   *  ADMIN SETTINGS TREE INTRODUCTION
      21   *
      22   *  This file performs the following tasks:
      23   *   -it defines the necessary objects and interfaces to build the Moodle
      24   *    admin hierarchy
      25   *   -it defines the admin_externalpage_setup()
      26   *
      27   *  ADMIN_SETTING OBJECTS
      28   *
      29   *  Moodle settings are represented by objects that inherit from the admin_setting
      30   *  class. These objects encapsulate how to read a setting, how to write a new value
      31   *  to a setting, and how to appropriately display the HTML to modify the setting.
      32   *
      33   *  ADMIN_SETTINGPAGE OBJECTS
      34   *
      35   *  The admin_setting objects are then grouped into admin_settingpages. The latter
      36   *  appear in the Moodle admin tree block. All interaction with admin_settingpage
      37   *  objects is handled by the admin/settings.php file.
      38   *
      39   *  ADMIN_EXTERNALPAGE OBJECTS
      40   *
      41   *  There are some settings in Moodle that are too complex to (efficiently) handle
      42   *  with admin_settingpages. (Consider, for example, user management and displaying
      43   *  lists of users.) In this case, we use the admin_externalpage object. This object
      44   *  places a link to an external PHP file in the admin tree block.
      45   *
      46   *  If you're using an admin_externalpage object for some settings, you can take
      47   *  advantage of the admin_externalpage_* functions. For example, suppose you wanted
      48   *  to add a foo.php file into admin. First off, you add the following line to
      49   *  admin/settings/first.php (at the end of the file) or to some other file in
      50   *  admin/settings:
      51   * <code>
      52   *     $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
      53   *         $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
      54   * </code>
      55   *
      56   *  Next, in foo.php, your file structure would resemble the following:
      57   * <code>
      58   *         require(dirname(dirname(dirname(__FILE__))).'/config.php');
      59   *         require_once($CFG->libdir.'/adminlib.php');
      60   *         admin_externalpage_setup('foo');
      61   *         // functionality like processing form submissions goes here
      62   *         echo $OUTPUT->header();
      63   *         // your HTML goes here
      64   *         echo $OUTPUT->footer();
      65   * </code>
      66   *
      67   *  The admin_externalpage_setup() function call ensures the user is logged in,
      68   *  and makes sure that they have the proper role permission to access the page.
      69   *  It also configures all $PAGE properties needed for navigation.
      70   *
      71   *  ADMIN_CATEGORY OBJECTS
      72   *
      73   *  Above and beyond all this, we have admin_category objects. These objects
      74   *  appear as folders in the admin tree block. They contain admin_settingpage's,
      75   *  admin_externalpage's, and other admin_category's.
      76   *
      77   *  OTHER NOTES
      78   *
      79   *  admin_settingpage's, admin_externalpage's, and admin_category's all inherit
      80   *  from part_of_admin_tree (a pseudointerface). This interface insists that
      81   *  a class has a check_access method for access permissions, a locate method
      82   *  used to find a specific node in the admin tree and find parent path.
      83   *
      84   *  admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
      85   *  interface ensures that the class implements a recursive add function which
      86   *  accepts a part_of_admin_tree object and searches for the proper place to
      87   *  put it. parentable_part_of_admin_tree implies part_of_admin_tree.
      88   *
      89   *  Please note that the $this->name field of any part_of_admin_tree must be
      90   *  UNIQUE throughout the ENTIRE admin tree.
      91   *
      92   *  The $this->name field of an admin_setting object (which is *not* part_of_
      93   *  admin_tree) must be unique on the respective admin_settingpage where it is
      94   *  used.
      95   *
      96   * Original author: Vincenzo K. Marcovecchio
      97   * Maintainer:      Petr Skoda
      98   *
      99   * @package    core
     100   * @subpackage admin
     101   * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
     102   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     103   */
     104  
     105  defined('MOODLE_INTERNAL') || die();
     106  
     107  /// Add libraries
     108  require_once($CFG->libdir.'/ddllib.php');
     109  require_once($CFG->libdir.'/xmlize.php');
     110  require_once($CFG->libdir.'/messagelib.php');
     111  
     112  define('INSECURE_DATAROOT_WARNING', 1);
     113  define('INSECURE_DATAROOT_ERROR', 2);
     114  
     115  /**
     116   * Automatically clean-up all plugin data and remove the plugin DB tables
     117   *
     118   * NOTE: do not call directly, use new /admin/plugins.php?uninstall=component instead!
     119   *
     120   * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
     121   * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
     122   * @uses global $OUTPUT to produce notices and other messages
     123   * @return void
     124   */
     125  function uninstall_plugin($type, $name) {
     126      global $CFG, $DB, $OUTPUT;
     127  
     128      // This may take a long time.
     129      core_php_time_limit::raise();
     130  
     131      // Recursively uninstall all subplugins first.
     132      $subplugintypes = core_component::get_plugin_types_with_subplugins();
     133      if (isset($subplugintypes[$type])) {
     134          $base = core_component::get_plugin_directory($type, $name);
     135          if (file_exists("$base/db/subplugins.php")) {
     136              $subplugins = array();
     137              include("$base/db/subplugins.php");
     138              foreach ($subplugins as $subplugintype=>$dir) {
     139                  $instances = core_component::get_plugin_list($subplugintype);
     140                  foreach ($instances as $subpluginname => $notusedpluginpath) {
     141                      uninstall_plugin($subplugintype, $subpluginname);
     142                  }
     143              }
     144          }
     145  
     146      }
     147  
     148      $component = $type . '_' . $name;  // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
     149  
     150      if ($type === 'mod') {
     151          $pluginname = $name;  // eg. 'forum'
     152          if (get_string_manager()->string_exists('modulename', $component)) {
     153              $strpluginname = get_string('modulename', $component);
     154          } else {
     155              $strpluginname = $component;
     156          }
     157  
     158      } else {
     159          $pluginname = $component;
     160          if (get_string_manager()->string_exists('pluginname', $component)) {
     161              $strpluginname = get_string('pluginname', $component);
     162          } else {
     163              $strpluginname = $component;
     164          }
     165      }
     166  
     167      echo $OUTPUT->heading($pluginname);
     168  
     169      // Delete all tag instances associated with this plugin.
     170      require_once($CFG->dirroot . '/tag/lib.php');
     171      tag_delete_instances($component);
     172  
     173      // Custom plugin uninstall.
     174      $plugindirectory = core_component::get_plugin_directory($type, $name);
     175      $uninstalllib = $plugindirectory . '/db/uninstall.php';
     176      if (file_exists($uninstalllib)) {
     177          require_once($uninstalllib);
     178          $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall';    // eg. 'xmldb_workshop_uninstall()'
     179          if (function_exists($uninstallfunction)) {
     180              // Do not verify result, let plugin complain if necessary.
     181              $uninstallfunction();
     182          }
     183      }
     184  
     185      // Specific plugin type cleanup.
     186      $plugininfo = core_plugin_manager::instance()->get_plugin_info($component);
     187      if ($plugininfo) {
     188          $plugininfo->uninstall_cleanup();
     189          core_plugin_manager::reset_caches();
     190      }
     191      $plugininfo = null;
     192  
     193      // perform clean-up task common for all the plugin/subplugin types
     194  
     195      //delete the web service functions and pre-built services
     196      require_once($CFG->dirroot.'/lib/externallib.php');
     197      external_delete_descriptions($component);
     198  
     199      // delete calendar events
     200      $DB->delete_records('event', array('modulename' => $pluginname));
     201  
     202      // Delete scheduled tasks.
     203      $DB->delete_records('task_scheduled', array('component' => $pluginname));
     204  
     205      // Delete Inbound Message datakeys.
     206      $DB->delete_records_select('messageinbound_datakeys',
     207              'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($pluginname));
     208  
     209      // Delete Inbound Message handlers.
     210      $DB->delete_records('messageinbound_handlers', array('component' => $pluginname));
     211  
     212      // delete all the logs
     213      $DB->delete_records('log', array('module' => $pluginname));
     214  
     215      // delete log_display information
     216      $DB->delete_records('log_display', array('component' => $component));
     217  
     218      // delete the module configuration records
     219      unset_all_config_for_plugin($component);
     220      if ($type === 'mod') {
     221          unset_all_config_for_plugin($pluginname);
     222      }
     223  
     224      // delete message provider
     225      message_provider_uninstall($component);
     226  
     227      // delete the plugin tables
     228      $xmldbfilepath = $plugindirectory . '/db/install.xml';
     229      drop_plugin_tables($component, $xmldbfilepath, false);
     230      if ($type === 'mod' or $type === 'block') {
     231          // non-frankenstyle table prefixes
     232          drop_plugin_tables($name, $xmldbfilepath, false);
     233      }
     234  
     235      // delete the capabilities that were defined by this module
     236      capabilities_cleanup($component);
     237  
     238      // remove event handlers and dequeue pending events
     239      events_uninstall($component);
     240  
     241      // Delete all remaining files in the filepool owned by the component.
     242      $fs = get_file_storage();
     243      $fs->delete_component_files($component);
     244  
     245      // Finally purge all caches.
     246      purge_all_caches();
     247  
     248      // Invalidate the hash used for upgrade detections.
     249      set_config('allversionshash', '');
     250  
     251      echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
     252  }
     253  
     254  /**
     255   * Returns the version of installed component
     256   *
     257   * @param string $component component name
     258   * @param string $source either 'disk' or 'installed' - where to get the version information from
     259   * @return string|bool version number or false if the component is not found
     260   */
     261  function get_component_version($component, $source='installed') {
     262      global $CFG, $DB;
     263  
     264      list($type, $name) = core_component::normalize_component($component);
     265  
     266      // moodle core or a core subsystem
     267      if ($type === 'core') {
     268          if ($source === 'installed') {
     269              if (empty($CFG->version)) {
     270                  return false;
     271              } else {
     272                  return $CFG->version;
     273              }
     274          } else {
     275              if (!is_readable($CFG->dirroot.'/version.php')) {
     276                  return false;
     277              } else {
     278                  $version = null; //initialize variable for IDEs
     279                  include($CFG->dirroot.'/version.php');
     280                  return $version;
     281              }
     282          }
     283      }
     284  
     285      // activity module
     286      if ($type === 'mod') {
     287          if ($source === 'installed') {
     288              if ($CFG->version < 2013092001.02) {
     289                  return $DB->get_field('modules', 'version', array('name'=>$name));
     290              } else {
     291                  return get_config('mod_'.$name, 'version');
     292              }
     293  
     294          } else {
     295              $mods = core_component::get_plugin_list('mod');
     296              if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
     297                  return false;
     298              } else {
     299                  $plugin = new stdClass();
     300                  $plugin->version = null;
     301                  $module = $plugin;
     302                  include($mods[$name].'/version.php');
     303                  return $plugin->version;
     304              }
     305          }
     306      }
     307  
     308      // block
     309      if ($type === 'block') {
     310          if ($source === 'installed') {
     311              if ($CFG->version < 2013092001.02) {
     312                  return $DB->get_field('block', 'version', array('name'=>$name));
     313              } else {
     314                  return get_config('block_'.$name, 'version');
     315              }
     316          } else {
     317              $blocks = core_component::get_plugin_list('block');
     318              if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
     319                  return false;
     320              } else {
     321                  $plugin = new stdclass();
     322                  include($blocks[$name].'/version.php');
     323                  return $plugin->version;
     324              }
     325          }
     326      }
     327  
     328      // all other plugin types
     329      if ($source === 'installed') {
     330          return get_config($type.'_'.$name, 'version');
     331      } else {
     332          $plugins = core_component::get_plugin_list($type);
     333          if (empty($plugins[$name])) {
     334              return false;
     335          } else {
     336              $plugin = new stdclass();
     337              include($plugins[$name].'/version.php');
     338              return $plugin->version;
     339          }
     340      }
     341  }
     342  
     343  /**
     344   * Delete all plugin tables
     345   *
     346   * @param string $name Name of plugin, used as table prefix
     347   * @param string $file Path to install.xml file
     348   * @param bool $feedback defaults to true
     349   * @return bool Always returns true
     350   */
     351  function drop_plugin_tables($name, $file, $feedback=true) {
     352      global $CFG, $DB;
     353  
     354      // first try normal delete
     355      if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
     356          return true;
     357      }
     358  
     359      // then try to find all tables that start with name and are not in any xml file
     360      $used_tables = get_used_table_names();
     361  
     362      $tables = $DB->get_tables();
     363  
     364      /// Iterate over, fixing id fields as necessary
     365      foreach ($tables as $table) {
     366          if (in_array($table, $used_tables)) {
     367              continue;
     368          }
     369  
     370          if (strpos($table, $name) !== 0) {
     371              continue;
     372          }
     373  
     374          // found orphan table --> delete it
     375          if ($DB->get_manager()->table_exists($table)) {
     376              $xmldb_table = new xmldb_table($table);
     377              $DB->get_manager()->drop_table($xmldb_table);
     378          }
     379      }
     380  
     381      return true;
     382  }
     383  
     384  /**
     385   * Returns names of all known tables == tables that moodle knows about.
     386   *
     387   * @return array Array of lowercase table names
     388   */
     389  function get_used_table_names() {
     390      $table_names = array();
     391      $dbdirs = get_db_directories();
     392  
     393      foreach ($dbdirs as $dbdir) {
     394          $file = $dbdir.'/install.xml';
     395  
     396          $xmldb_file = new xmldb_file($file);
     397  
     398          if (!$xmldb_file->fileExists()) {
     399              continue;
     400          }
     401  
     402          $loaded    = $xmldb_file->loadXMLStructure();
     403          $structure = $xmldb_file->getStructure();
     404  
     405          if ($loaded and $tables = $structure->getTables()) {
     406              foreach($tables as $table) {
     407                  $table_names[] = strtolower($table->getName());
     408              }
     409          }
     410      }
     411  
     412      return $table_names;
     413  }
     414  
     415  /**
     416   * Returns list of all directories where we expect install.xml files
     417   * @return array Array of paths
     418   */
     419  function get_db_directories() {
     420      global $CFG;
     421  
     422      $dbdirs = array();
     423  
     424      /// First, the main one (lib/db)
     425      $dbdirs[] = $CFG->libdir.'/db';
     426  
     427      /// Then, all the ones defined by core_component::get_plugin_types()
     428      $plugintypes = core_component::get_plugin_types();
     429      foreach ($plugintypes as $plugintype => $pluginbasedir) {
     430          if ($plugins = core_component::get_plugin_list($plugintype)) {
     431              foreach ($plugins as $plugin => $plugindir) {
     432                  $dbdirs[] = $plugindir.'/db';
     433              }
     434          }
     435      }
     436  
     437      return $dbdirs;
     438  }
     439  
     440  /**
     441   * Try to obtain or release the cron lock.
     442   * @param string  $name  name of lock
     443   * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionally
     444   * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
     445   * @return bool true if lock obtained
     446   */
     447  function set_cron_lock($name, $until, $ignorecurrent=false) {
     448      global $DB;
     449      if (empty($name)) {
     450          debugging("Tried to get a cron lock for a null fieldname");
     451          return false;
     452      }
     453  
     454      // remove lock by force == remove from config table
     455      if (is_null($until)) {
     456          set_config($name, null);
     457          return true;
     458      }
     459  
     460      if (!$ignorecurrent) {
     461          // read value from db - other processes might have changed it
     462          $value = $DB->get_field('config', 'value', array('name'=>$name));
     463  
     464          if ($value and $value > time()) {
     465              //lock active
     466              return false;
     467          }
     468      }
     469  
     470      set_config($name, $until);
     471      return true;
     472  }
     473  
     474  /**
     475   * Test if and critical warnings are present
     476   * @return bool
     477   */
     478  function admin_critical_warnings_present() {
     479      global $SESSION;
     480  
     481      if (!has_capability('moodle/site:config', context_system::instance())) {
     482          return 0;
     483      }
     484  
     485      if (!isset($SESSION->admin_critical_warning)) {
     486          $SESSION->admin_critical_warning = 0;
     487          if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
     488              $SESSION->admin_critical_warning = 1;
     489          }
     490      }
     491  
     492      return $SESSION->admin_critical_warning;
     493  }
     494  
     495  /**
     496   * Detects if float supports at least 10 decimal digits
     497   *
     498   * Detects if float supports at least 10 decimal digits
     499   * and also if float-->string conversion works as expected.
     500   *
     501   * @return bool true if problem found
     502   */
     503  function is_float_problem() {
     504      $num1 = 2009010200.01;
     505      $num2 = 2009010200.02;
     506  
     507      return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
     508  }
     509  
     510  /**
     511   * Try to verify that dataroot is not accessible from web.
     512   *
     513   * Try to verify that dataroot is not accessible from web.
     514   * It is not 100% correct but might help to reduce number of vulnerable sites.
     515   * Protection from httpd.conf and .htaccess is not detected properly.
     516   *
     517   * @uses INSECURE_DATAROOT_WARNING
     518   * @uses INSECURE_DATAROOT_ERROR
     519   * @param bool $fetchtest try to test public access by fetching file, default false
     520   * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
     521   */
     522  function is_dataroot_insecure($fetchtest=false) {
     523      global $CFG;
     524  
     525      $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
     526  
     527      $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
     528      $rp = strrev(trim($rp, '/'));
     529      $rp = explode('/', $rp);
     530      foreach($rp as $r) {
     531          if (strpos($siteroot, '/'.$r.'/') === 0) {
     532              $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
     533          } else {
     534              break; // probably alias root
     535          }
     536      }
     537  
     538      $siteroot = strrev($siteroot);
     539      $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
     540  
     541      if (strpos($dataroot, $siteroot) !== 0) {
     542          return false;
     543      }
     544  
     545      if (!$fetchtest) {
     546          return INSECURE_DATAROOT_WARNING;
     547      }
     548  
     549      // now try all methods to fetch a test file using http protocol
     550  
     551      $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
     552      preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
     553      $httpdocroot = $matches[1];
     554      $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
     555      make_upload_directory('diag');
     556      $testfile = $CFG->dataroot.'/diag/public.txt';
     557      if (!file_exists($testfile)) {
     558          file_put_contents($testfile, 'test file, do not delete');
     559          @chmod($testfile, $CFG->filepermissions);
     560      }
     561      $teststr = trim(file_get_contents($testfile));
     562      if (empty($teststr)) {
     563      // hmm, strange
     564          return INSECURE_DATAROOT_WARNING;
     565      }
     566  
     567      $testurl = $datarooturl.'/diag/public.txt';
     568      if (extension_loaded('curl') and
     569          !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
     570          !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
     571          ($ch = @curl_init($testurl)) !== false) {
     572          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
     573          curl_setopt($ch, CURLOPT_HEADER, false);
     574          $data = curl_exec($ch);
     575          if (!curl_errno($ch)) {
     576              $data = trim($data);
     577              if ($data === $teststr) {
     578                  curl_close($ch);
     579                  return INSECURE_DATAROOT_ERROR;
     580              }
     581          }
     582          curl_close($ch);
     583      }
     584  
     585      if ($data = @file_get_contents($testurl)) {
     586          $data = trim($data);
     587          if ($data === $teststr) {
     588              return INSECURE_DATAROOT_ERROR;
     589          }
     590      }
     591  
     592      preg_match('|https?://([^/]+)|i', $testurl, $matches);
     593      $sitename = $matches[1];
     594      $error = 0;
     595      if ($fp = @fsockopen($sitename, 80, $error)) {
     596          preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
     597          $localurl = $matches[1];
     598          $out = "GET $localurl HTTP/1.1\r\n";
     599          $out .= "Host: $sitename\r\n";
     600          $out .= "Connection: Close\r\n\r\n";
     601          fwrite($fp, $out);
     602          $data = '';
     603          $incoming = false;
     604          while (!feof($fp)) {
     605              if ($incoming) {
     606                  $data .= fgets($fp, 1024);
     607              } else if (@fgets($fp, 1024) === "\r\n") {
     608                      $incoming = true;
     609                  }
     610          }
     611          fclose($fp);
     612          $data = trim($data);
     613          if ($data === $teststr) {
     614              return INSECURE_DATAROOT_ERROR;
     615          }
     616      }
     617  
     618      return INSECURE_DATAROOT_WARNING;
     619  }
     620  
     621  /**
     622   * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
     623   */
     624  function enable_cli_maintenance_mode() {
     625      global $CFG;
     626  
     627      if (file_exists("$CFG->dataroot/climaintenance.html")) {
     628          unlink("$CFG->dataroot/climaintenance.html");
     629      }
     630  
     631      if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
     632          $data = $CFG->maintenance_message;
     633          $data = bootstrap_renderer::early_error_content($data, null, null, null);
     634          $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
     635  
     636      } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
     637          $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
     638  
     639      } else {
     640          $data = get_string('sitemaintenance', 'admin');
     641          $data = bootstrap_renderer::early_error_content($data, null, null, null);
     642          $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
     643      }
     644  
     645      file_put_contents("$CFG->dataroot/climaintenance.html", $data);
     646      chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
     647  }
     648  
     649  /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
     650  
     651  
     652  /**
     653   * Interface for anything appearing in the admin tree
     654   *
     655   * The interface that is implemented by anything that appears in the admin tree
     656   * block. It forces inheriting classes to define a method for checking user permissions
     657   * and methods for finding something in the admin tree.
     658   *
     659   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     660   */
     661  interface part_of_admin_tree {
     662  
     663  /**
     664   * Finds a named part_of_admin_tree.
     665   *
     666   * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
     667   * and not parentable_part_of_admin_tree, then this function should only check if
     668   * $this->name matches $name. If it does, it should return a reference to $this,
     669   * otherwise, it should return a reference to NULL.
     670   *
     671   * If a class inherits parentable_part_of_admin_tree, this method should be called
     672   * recursively on all child objects (assuming, of course, the parent object's name
     673   * doesn't match the search criterion).
     674   *
     675   * @param string $name The internal name of the part_of_admin_tree we're searching for.
     676   * @return mixed An object reference or a NULL reference.
     677   */
     678      public function locate($name);
     679  
     680      /**
     681       * Removes named part_of_admin_tree.
     682       *
     683       * @param string $name The internal name of the part_of_admin_tree we want to remove.
     684       * @return bool success.
     685       */
     686      public function prune($name);
     687  
     688      /**
     689       * Search using query
     690       * @param string $query
     691       * @return mixed array-object structure of found settings and pages
     692       */
     693      public function search($query);
     694  
     695      /**
     696       * Verifies current user's access to this part_of_admin_tree.
     697       *
     698       * Used to check if the current user has access to this part of the admin tree or
     699       * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
     700       * then this method is usually just a call to has_capability() in the site context.
     701       *
     702       * If a class inherits parentable_part_of_admin_tree, this method should return the
     703       * logical OR of the return of check_access() on all child objects.
     704       *
     705       * @return bool True if the user has access, false if she doesn't.
     706       */
     707      public function check_access();
     708  
     709      /**
     710       * Mostly useful for removing of some parts of the tree in admin tree block.
     711       *
     712       * @return True is hidden from normal list view
     713       */
     714      public function is_hidden();
     715  
     716      /**
     717       * Show we display Save button at the page bottom?
     718       * @return bool
     719       */
     720      public function show_save();
     721  }
     722  
     723  
     724  /**
     725   * Interface implemented by any part_of_admin_tree that has children.
     726   *
     727   * The interface implemented by any part_of_admin_tree that can be a parent
     728   * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
     729   * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
     730   * include an add method for adding other part_of_admin_tree objects as children.
     731   *
     732   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     733   */
     734  interface parentable_part_of_admin_tree extends part_of_admin_tree {
     735  
     736  /**
     737   * Adds a part_of_admin_tree object to the admin tree.
     738   *
     739   * Used to add a part_of_admin_tree object to this object or a child of this
     740   * object. $something should only be added if $destinationname matches
     741   * $this->name. If it doesn't, add should be called on child objects that are
     742   * also parentable_part_of_admin_tree's.
     743   *
     744   * $something should be appended as the last child in the $destinationname. If the
     745   * $beforesibling is specified, $something should be prepended to it. If the given
     746   * sibling is not found, $something should be appended to the end of $destinationname
     747   * and a developer debugging message should be displayed.
     748   *
     749   * @param string $destinationname The internal name of the new parent for $something.
     750   * @param part_of_admin_tree $something The object to be added.
     751   * @return bool True on success, false on failure.
     752   */
     753      public function add($destinationname, $something, $beforesibling = null);
     754  
     755  }
     756  
     757  
     758  /**
     759   * The object used to represent folders (a.k.a. categories) in the admin tree block.
     760   *
     761   * Each admin_category object contains a number of part_of_admin_tree objects.
     762   *
     763   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     764   */
     765  class admin_category implements parentable_part_of_admin_tree {
     766  
     767      /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */
     768      protected $children;
     769      /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
     770      public $name;
     771      /** @var string The displayed name for this category. Usually obtained through get_string() */
     772      public $visiblename;
     773      /** @var bool Should this category be hidden in admin tree block? */
     774      public $hidden;
     775      /** @var mixed Either a string or an array or strings */
     776      public $path;
     777      /** @var mixed Either a string or an array or strings */
     778      public $visiblepath;
     779  
     780      /** @var array fast lookup category cache, all categories of one tree point to one cache */
     781      protected $category_cache;
     782  
     783      /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */
     784      protected $sort = false;
     785      /** @var bool If set to true children will be sorted in ascending order. */
     786      protected $sortasc = true;
     787      /** @var bool If set to true sub categories and pages will be split and then sorted.. */
     788      protected $sortsplit = true;
     789      /** @var bool $sorted True if the children have been sorted and don't need resorting */
     790      protected $sorted = false;
     791  
     792      /**
     793       * Constructor for an empty admin category
     794       *
     795       * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
     796       * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
     797       * @param bool $hidden hide category in admin tree block, defaults to false
     798       */
     799      public function __construct($name, $visiblename, $hidden=false) {
     800          $this->children    = array();
     801          $this->name        = $name;
     802          $this->visiblename = $visiblename;
     803          $this->hidden      = $hidden;
     804      }
     805  
     806      /**
     807       * Returns a reference to the part_of_admin_tree object with internal name $name.
     808       *
     809       * @param string $name The internal name of the object we want.
     810       * @param bool $findpath initialize path and visiblepath arrays
     811       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
     812       *                  defaults to false
     813       */
     814      public function locate($name, $findpath=false) {
     815          if (!isset($this->category_cache[$this->name])) {
     816              // somebody much have purged the cache
     817              $this->category_cache[$this->name] = $this;
     818          }
     819  
     820          if ($this->name == $name) {
     821              if ($findpath) {
     822                  $this->visiblepath[] = $this->visiblename;
     823                  $this->path[]        = $this->name;
     824              }
     825              return $this;
     826          }
     827  
     828          // quick category lookup
     829          if (!$findpath and isset($this->category_cache[$name])) {
     830              return $this->category_cache[$name];
     831          }
     832  
     833          $return = NULL;
     834          foreach($this->children as $childid=>$unused) {
     835              if ($return = $this->children[$childid]->locate($name, $findpath)) {
     836                  break;
     837              }
     838          }
     839  
     840          if (!is_null($return) and $findpath) {
     841              $return->visiblepath[] = $this->visiblename;
     842              $return->path[]        = $this->name;
     843          }
     844  
     845          return $return;
     846      }
     847  
     848      /**
     849       * Search using query
     850       *
     851       * @param string query
     852       * @return mixed array-object structure of found settings and pages
     853       */
     854      public function search($query) {
     855          $result = array();
     856          foreach ($this->get_children() as $child) {
     857              $subsearch = $child->search($query);
     858              if (!is_array($subsearch)) {
     859                  debugging('Incorrect search result from '.$child->name);
     860                  continue;
     861              }
     862              $result = array_merge($result, $subsearch);
     863          }
     864          return $result;
     865      }
     866  
     867      /**
     868       * Removes part_of_admin_tree object with internal name $name.
     869       *
     870       * @param string $name The internal name of the object we want to remove.
     871       * @return bool success
     872       */
     873      public function prune($name) {
     874  
     875          if ($this->name == $name) {
     876              return false;  //can not remove itself
     877          }
     878  
     879          foreach($this->children as $precedence => $child) {
     880              if ($child->name == $name) {
     881                  // clear cache and delete self
     882                  while($this->category_cache) {
     883                      // delete the cache, but keep the original array address
     884                      array_pop($this->category_cache);
     885                  }
     886                  unset($this->children[$precedence]);
     887                  return true;
     888              } else if ($this->children[$precedence]->prune($name)) {
     889                  return true;
     890              }
     891          }
     892          return false;
     893      }
     894  
     895      /**
     896       * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
     897       *
     898       * By default the new part of the tree is appended as the last child of the parent. You
     899       * can specify a sibling node that the new part should be prepended to. If the given
     900       * sibling is not found, the part is appended to the end (as it would be by default) and
     901       * a developer debugging message is displayed.
     902       *
     903       * @throws coding_exception if the $beforesibling is empty string or is not string at all.
     904       * @param string $destinationame The internal name of the immediate parent that we want for $something.
     905       * @param mixed $something A part_of_admin_tree or setting instance to be added.
     906       * @param string $beforesibling The name of the parent's child the $something should be prepended to.
     907       * @return bool True if successfully added, false if $something can not be added.
     908       */
     909      public function add($parentname, $something, $beforesibling = null) {
     910          global $CFG;
     911  
     912          $parent = $this->locate($parentname);
     913          if (is_null($parent)) {
     914              debugging('parent does not exist!');
     915              return false;
     916          }
     917  
     918          if ($something instanceof part_of_admin_tree) {
     919              if (!($parent instanceof parentable_part_of_admin_tree)) {
     920                  debugging('error - parts of tree can be inserted only into parentable parts');
     921                  return false;
     922              }
     923              if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
     924                  // The name of the node is already used, simply warn the developer that this should not happen.
     925                  // It is intentional to check for the debug level before performing the check.
     926                  debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
     927              }
     928              if (is_null($beforesibling)) {
     929                  // Append $something as the parent's last child.
     930                  $parent->children[] = $something;
     931              } else {
     932                  if (!is_string($beforesibling) or trim($beforesibling) === '') {
     933                      throw new coding_exception('Unexpected value of the beforesibling parameter');
     934                  }
     935                  // Try to find the position of the sibling.
     936                  $siblingposition = null;
     937                  foreach ($parent->children as $childposition => $child) {
     938                      if ($child->name === $beforesibling) {
     939                          $siblingposition = $childposition;
     940                          break;
     941                      }
     942                  }
     943                  if (is_null($siblingposition)) {
     944                      debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
     945                      $parent->children[] = $something;
     946                  } else {
     947                      $parent->children = array_merge(
     948                          array_slice($parent->children, 0, $siblingposition),
     949                          array($something),
     950                          array_slice($parent->children, $siblingposition)
     951                      );
     952                  }
     953              }
     954              if ($something instanceof admin_category) {
     955                  if (isset($this->category_cache[$something->name])) {
     956                      debugging('Duplicate admin category name: '.$something->name);
     957                  } else {
     958                      $this->category_cache[$something->name] = $something;
     959                      $something->category_cache =& $this->category_cache;
     960                      foreach ($something->children as $child) {
     961                          // just in case somebody already added subcategories
     962                          if ($child instanceof admin_category) {
     963                              if (isset($this->category_cache[$child->name])) {
     964                                  debugging('Duplicate admin category name: '.$child->name);
     965                              } else {
     966                                  $this->category_cache[$child->name] = $child;
     967                                  $child->category_cache =& $this->category_cache;
     968                              }
     969                          }
     970                      }
     971                  }
     972              }
     973              return true;
     974  
     975          } else {
     976              debugging('error - can not add this element');
     977              return false;
     978          }
     979  
     980      }
     981  
     982      /**
     983       * Checks if the user has access to anything in this category.
     984       *
     985       * @return bool True if the user has access to at least one child in this category, false otherwise.
     986       */
     987      public function check_access() {
     988          foreach ($this->children as $child) {
     989              if ($child->check_access()) {
     990                  return true;
     991              }
     992          }
     993          return false;
     994      }
     995  
     996      /**
     997       * Is this category hidden in admin tree block?
     998       *
     999       * @return bool True if hidden
    1000       */
    1001      public function is_hidden() {
    1002          return $this->hidden;
    1003      }
    1004  
    1005      /**
    1006       * Show we display Save button at the page bottom?
    1007       * @return bool
    1008       */
    1009      public function show_save() {
    1010          foreach ($this->children as $child) {
    1011              if ($child->show_save()) {
    1012                  return true;
    1013              }
    1014          }
    1015          return false;
    1016      }
    1017  
    1018      /**
    1019       * Sets sorting on this category.
    1020       *
    1021       * Please note this function doesn't actually do the sorting.
    1022       * It can be called anytime.
    1023       * Sorting occurs when the user calls get_children.
    1024       * Code using the children array directly won't see the sorted results.
    1025       *
    1026       * @param bool $sort If set to true children will be sorted, if false they won't be.
    1027       * @param bool $asc If true sorting will be ascending, otherwise descending.
    1028       * @param bool $split If true we sort pages and sub categories separately.
    1029       */
    1030      public function set_sorting($sort, $asc = true, $split = true) {
    1031          $this->sort = (bool)$sort;
    1032          $this->sortasc = (bool)$asc;
    1033          $this->sortsplit = (bool)$split;
    1034      }
    1035  
    1036      /**
    1037       * Returns the children associated with this category.
    1038       *
    1039       * @return part_of_admin_tree[]
    1040       */
    1041      public function get_children() {
    1042          // If we should sort and it hasn't already been sorted.
    1043          if ($this->sort && !$this->sorted) {
    1044              if ($this->sortsplit) {
    1045                  $categories = array();
    1046                  $pages = array();
    1047                  foreach ($this->children as $child) {
    1048                      if ($child instanceof admin_category) {
    1049                          $categories[] = $child;
    1050                      } else {
    1051                          $pages[] = $child;
    1052                      }
    1053                  }
    1054                  core_collator::asort_objects_by_property($categories, 'visiblename');
    1055                  core_collator::asort_objects_by_property($pages, 'visiblename');
    1056                  if (!$this->sortasc) {
    1057                      $categories = array_reverse($categories);
    1058                      $pages = array_reverse($pages);
    1059                  }
    1060                  $this->children = array_merge($pages, $categories);
    1061              } else {
    1062                  core_collator::asort_objects_by_property($this->children, 'visiblename');
    1063                  if (!$this->sortasc) {
    1064                      $this->children = array_reverse($this->children);
    1065                  }
    1066              }
    1067              $this->sorted = true;
    1068          }
    1069          return $this->children;
    1070      }
    1071  
    1072      /**
    1073       * Magically gets a property from this object.
    1074       *
    1075       * @param $property
    1076       * @return part_of_admin_tree[]
    1077       * @throws coding_exception
    1078       */
    1079      public function __get($property) {
    1080          if ($property === 'children') {
    1081              return $this->get_children();
    1082          }
    1083          throw new coding_exception('Invalid property requested.');
    1084      }
    1085  
    1086      /**
    1087       * Magically sets a property against this object.
    1088       *
    1089       * @param string $property
    1090       * @param mixed $value
    1091       * @throws coding_exception
    1092       */
    1093      public function __set($property, $value) {
    1094          if ($property === 'children') {
    1095              $this->sorted = false;
    1096              $this->children = $value;
    1097          } else {
    1098              throw new coding_exception('Invalid property requested.');
    1099          }
    1100      }
    1101  
    1102      /**
    1103       * Checks if an inaccessible property is set.
    1104       *
    1105       * @param string $property
    1106       * @return bool
    1107       * @throws coding_exception
    1108       */
    1109      public function __isset($property) {
    1110          if ($property === 'children') {
    1111              return isset($this->children);
    1112          }
    1113          throw new coding_exception('Invalid property requested.');
    1114      }
    1115  }
    1116  
    1117  
    1118  /**
    1119   * Root of admin settings tree, does not have any parent.
    1120   *
    1121   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1122   */
    1123  class admin_root extends admin_category {
    1124  /** @var array List of errors */
    1125      public $errors;
    1126      /** @var string search query */
    1127      public $search;
    1128      /** @var bool full tree flag - true means all settings required, false only pages required */
    1129      public $fulltree;
    1130      /** @var bool flag indicating loaded tree */
    1131      public $loaded;
    1132      /** @var mixed site custom defaults overriding defaults in settings files*/
    1133      public $custom_defaults;
    1134  
    1135      /**
    1136       * @param bool $fulltree true means all settings required,
    1137       *                            false only pages required
    1138       */
    1139      public function __construct($fulltree) {
    1140          global $CFG;
    1141  
    1142          parent::__construct('root', get_string('administration'), false);
    1143          $this->errors   = array();
    1144          $this->search   = '';
    1145          $this->fulltree = $fulltree;
    1146          $this->loaded   = false;
    1147  
    1148          $this->category_cache = array();
    1149  
    1150          // load custom defaults if found
    1151          $this->custom_defaults = null;
    1152          $defaultsfile = "$CFG->dirroot/local/defaults.php";
    1153          if (is_readable($defaultsfile)) {
    1154              $defaults = array();
    1155              include($defaultsfile);
    1156              if (is_array($defaults) and count($defaults)) {
    1157                  $this->custom_defaults = $defaults;
    1158              }
    1159          }
    1160      }
    1161  
    1162      /**
    1163       * Empties children array, and sets loaded to false
    1164       *
    1165       * @param bool $requirefulltree
    1166       */
    1167      public function purge_children($requirefulltree) {
    1168          $this->children = array();
    1169          $this->fulltree = ($requirefulltree || $this->fulltree);
    1170          $this->loaded   = false;
    1171          //break circular dependencies - this helps PHP 5.2
    1172          while($this->category_cache) {
    1173              array_pop($this->category_cache);
    1174          }
    1175          $this->category_cache = array();
    1176      }
    1177  }
    1178  
    1179  
    1180  /**
    1181   * Links external PHP pages into the admin tree.
    1182   *
    1183   * See detailed usage example at the top of this document (adminlib.php)
    1184   *
    1185   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1186   */
    1187  class admin_externalpage implements part_of_admin_tree {
    1188  
    1189      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
    1190      public $name;
    1191  
    1192      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
    1193      public $visiblename;
    1194  
    1195      /** @var string The external URL that we should link to when someone requests this external page. */
    1196      public $url;
    1197  
    1198      /** @var string The role capability/permission a user must have to access this external page. */
    1199      public $req_capability;
    1200  
    1201      /** @var object The context in which capability/permission should be checked, default is site context. */
    1202      public $context;
    1203  
    1204      /** @var bool hidden in admin tree block. */
    1205      public $hidden;
    1206  
    1207      /** @var mixed either string or array of string */
    1208      public $path;
    1209  
    1210      /** @var array list of visible names of page parents */
    1211      public $visiblepath;
    1212  
    1213      /**
    1214       * Constructor for adding an external page into the admin tree.
    1215       *
    1216       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
    1217       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
    1218       * @param string $url The external URL that we should link to when someone requests this external page.
    1219       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
    1220       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
    1221       * @param stdClass $context The context the page relates to. Not sure what happens
    1222       *      if you specify something other than system or front page. Defaults to system.
    1223       */
    1224      public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
    1225          $this->name        = $name;
    1226          $this->visiblename = $visiblename;
    1227          $this->url         = $url;
    1228          if (is_array($req_capability)) {
    1229              $this->req_capability = $req_capability;
    1230          } else {
    1231              $this->req_capability = array($req_capability);
    1232          }
    1233          $this->hidden = $hidden;
    1234          $this->context = $context;
    1235      }
    1236  
    1237      /**
    1238       * Returns a reference to the part_of_admin_tree object with internal name $name.
    1239       *
    1240       * @param string $name The internal name of the object we want.
    1241       * @param bool $findpath defaults to false
    1242       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
    1243       */
    1244      public function locate($name, $findpath=false) {
    1245          if ($this->name == $name) {
    1246              if ($findpath) {
    1247                  $this->visiblepath = array($this->visiblename);
    1248                  $this->path        = array($this->name);
    1249              }
    1250              return $this;
    1251          } else {
    1252              $return = NULL;
    1253              return $return;
    1254          }
    1255      }
    1256  
    1257      /**
    1258       * This function always returns false, required function by interface
    1259       *
    1260       * @param string $name
    1261       * @return false
    1262       */
    1263      public function prune($name) {
    1264          return false;
    1265      }
    1266  
    1267      /**
    1268       * Search using query
    1269       *
    1270       * @param string $query
    1271       * @return mixed array-object structure of found settings and pages
    1272       */
    1273      public function search($query) {
    1274          $found = false;
    1275          if (strpos(strtolower($this->name), $query) !== false) {
    1276              $found = true;
    1277          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
    1278                  $found = true;
    1279              }
    1280          if ($found) {
    1281              $result = new stdClass();
    1282              $result->page     = $this;
    1283              $result->settings = array();
    1284              return array($this->name => $result);
    1285          } else {
    1286              return array();
    1287          }
    1288      }
    1289  
    1290      /**
    1291       * Determines if the current user has access to this external page based on $this->req_capability.
    1292       *
    1293       * @return bool True if user has access, false otherwise.
    1294       */
    1295      public function check_access() {
    1296          global $CFG;
    1297          $context = empty($this->context) ? context_system::instance() : $this->context;
    1298          foreach($this->req_capability as $cap) {
    1299              if (has_capability($cap, $context)) {
    1300                  return true;
    1301              }
    1302          }
    1303          return false;
    1304      }
    1305  
    1306      /**
    1307       * Is this external page hidden in admin tree block?
    1308       *
    1309       * @return bool True if hidden
    1310       */
    1311      public function is_hidden() {
    1312          return $this->hidden;
    1313      }
    1314  
    1315      /**
    1316       * Show we display Save button at the page bottom?
    1317       * @return bool
    1318       */
    1319      public function show_save() {
    1320          return false;
    1321      }
    1322  }
    1323  
    1324  
    1325  /**
    1326   * Used to group a number of admin_setting objects into a page and add them to the admin tree.
    1327   *
    1328   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1329   */
    1330  class admin_settingpage implements part_of_admin_tree {
    1331  
    1332      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
    1333      public $name;
    1334  
    1335      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
    1336      public $visiblename;
    1337  
    1338      /** @var mixed An array of admin_setting objects that are part of this setting page. */
    1339      public $settings;
    1340  
    1341      /** @var string The role capability/permission a user must have to access this external page. */
    1342      public $req_capability;
    1343  
    1344      /** @var object The context in which capability/permission should be checked, default is site context. */
    1345      public $context;
    1346  
    1347      /** @var bool hidden in admin tree block. */
    1348      public $hidden;
    1349  
    1350      /** @var mixed string of paths or array of strings of paths */
    1351      public $path;
    1352  
    1353      /** @var array list of visible names of page parents */
    1354      public $visiblepath;
    1355  
    1356      /**
    1357       * see admin_settingpage for details of this function
    1358       *
    1359       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
    1360       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
    1361       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
    1362       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
    1363       * @param stdClass $context The context the page relates to. Not sure what happens
    1364       *      if you specify something other than system or front page. Defaults to system.
    1365       */
    1366      public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
    1367          $this->settings    = new stdClass();
    1368          $this->name        = $name;
    1369          $this->visiblename = $visiblename;
    1370          if (is_array($req_capability)) {
    1371              $this->req_capability = $req_capability;
    1372          } else {
    1373              $this->req_capability = array($req_capability);
    1374          }
    1375          $this->hidden      = $hidden;
    1376          $this->context     = $context;
    1377      }
    1378  
    1379      /**
    1380       * see admin_category
    1381       *
    1382       * @param string $name
    1383       * @param bool $findpath
    1384       * @return mixed Object (this) if name ==  this->name, else returns null
    1385       */
    1386      public function locate($name, $findpath=false) {
    1387          if ($this->name == $name) {
    1388              if ($findpath) {
    1389                  $this->visiblepath = array($this->visiblename);
    1390                  $this->path        = array($this->name);
    1391              }
    1392              return $this;
    1393          } else {
    1394              $return = NULL;
    1395              return $return;
    1396          }
    1397      }
    1398  
    1399      /**
    1400       * Search string in settings page.
    1401       *
    1402       * @param string $query
    1403       * @return array
    1404       */
    1405      public function search($query) {
    1406          $found = array();
    1407  
    1408          foreach ($this->settings as $setting) {
    1409              if ($setting->is_related($query)) {
    1410                  $found[] = $setting;
    1411              }
    1412          }
    1413  
    1414          if ($found) {
    1415              $result = new stdClass();
    1416              $result->page     = $this;
    1417              $result->settings = $found;
    1418              return array($this->name => $result);
    1419          }
    1420  
    1421          $found = false;
    1422          if (strpos(strtolower($this->name), $query) !== false) {
    1423              $found = true;
    1424          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
    1425                  $found = true;
    1426              }
    1427          if ($found) {
    1428              $result = new stdClass();
    1429              $result->page     = $this;
    1430              $result->settings = array();
    1431              return array($this->name => $result);
    1432          } else {
    1433              return array();
    1434          }
    1435      }
    1436  
    1437      /**
    1438       * This function always returns false, required by interface
    1439       *
    1440       * @param string $name
    1441       * @return bool Always false
    1442       */
    1443      public function prune($name) {
    1444          return false;
    1445      }
    1446  
    1447      /**
    1448       * adds an admin_setting to this admin_settingpage
    1449       *
    1450       * 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
    1451       * n.b. each admin_setting in an admin_settingpage must have a unique internal name
    1452       *
    1453       * @param object $setting is the admin_setting object you want to add
    1454       * @return bool true if successful, false if not
    1455       */
    1456      public function add($setting) {
    1457          if (!($setting instanceof admin_setting)) {
    1458              debugging('error - not a setting instance');
    1459              return false;
    1460          }
    1461  
    1462          $this->settings->{$setting->name} = $setting;
    1463          return true;
    1464      }
    1465  
    1466      /**
    1467       * see admin_externalpage
    1468       *
    1469       * @return bool Returns true for yes false for no
    1470       */
    1471      public function check_access() {
    1472          global $CFG;
    1473          $context = empty($this->context) ? context_system::instance() : $this->context;
    1474          foreach($this->req_capability as $cap) {
    1475              if (has_capability($cap, $context)) {
    1476                  return true;
    1477              }
    1478          }
    1479          return false;
    1480      }
    1481  
    1482      /**
    1483       * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
    1484       * @return string Returns an XHTML string
    1485       */
    1486      public function output_html() {
    1487          $adminroot = admin_get_root();
    1488          $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
    1489          foreach($this->settings as $setting) {
    1490              $fullname = $setting->get_full_name();
    1491              if (array_key_exists($fullname, $adminroot->errors)) {
    1492                  $data = $adminroot->errors[$fullname]->data;
    1493              } else {
    1494                  $data = $setting->get_setting();
    1495                  // do not use defaults if settings not available - upgrade settings handles the defaults!
    1496              }
    1497              $return .= $setting->output_html($data);
    1498          }
    1499          $return .= '</fieldset>';
    1500          return $return;
    1501      }
    1502  
    1503      /**
    1504       * Is this settings page hidden in admin tree block?
    1505       *
    1506       * @return bool True if hidden
    1507       */
    1508      public function is_hidden() {
    1509          return $this->hidden;
    1510      }
    1511  
    1512      /**
    1513       * Show we display Save button at the page bottom?
    1514       * @return bool
    1515       */
    1516      public function show_save() {
    1517          foreach($this->settings as $setting) {
    1518              if (empty($setting->nosave)) {
    1519                  return true;
    1520              }
    1521          }
    1522          return false;
    1523      }
    1524  }
    1525  
    1526  
    1527  /**
    1528   * Admin settings class. Only exists on setting pages.
    1529   * Read & write happens at this level; no authentication.
    1530   *
    1531   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1532   */
    1533  abstract class admin_setting {
    1534      /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
    1535      public $name;
    1536      /** @var string localised name */
    1537      public $visiblename;
    1538      /** @var string localised long description in Markdown format */
    1539      public $description;
    1540      /** @var mixed Can be string or array of string */
    1541      public $defaultsetting;
    1542      /** @var string */
    1543      public $updatedcallback;
    1544      /** @var mixed can be String or Null.  Null means main config table */
    1545      public $plugin; // null means main config table
    1546      /** @var bool true indicates this setting does not actually save anything, just information */
    1547      public $nosave = false;
    1548      /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
    1549      public $affectsmodinfo = false;
    1550      /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */
    1551      private $flags = array();
    1552  
    1553      /**
    1554       * Constructor
    1555       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
    1556       *                     or 'myplugin/mysetting' for ones in config_plugins.
    1557       * @param string $visiblename localised name
    1558       * @param string $description localised long description
    1559       * @param mixed $defaultsetting string or array depending on implementation
    1560       */
    1561      public function __construct($name, $visiblename, $description, $defaultsetting) {
    1562          $this->parse_setting_name($name);
    1563          $this->visiblename    = $visiblename;
    1564          $this->description    = $description;
    1565          $this->defaultsetting = $defaultsetting;
    1566      }
    1567  
    1568      /**
    1569       * Generic function to add a flag to this admin setting.
    1570       *
    1571       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
    1572       * @param bool $default - The default for the flag
    1573       * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name.
    1574       * @param string $displayname - The display name for this flag. Used as a label next to the checkbox.
    1575       */
    1576      protected function set_flag_options($enabled, $default, $shortname, $displayname) {
    1577          if (empty($this->flags[$shortname])) {
    1578              $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname);
    1579          } else {
    1580              $this->flags[$shortname]->set_options($enabled, $default);
    1581          }
    1582      }
    1583  
    1584      /**
    1585       * Set the enabled options flag on this admin setting.
    1586       *
    1587       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
    1588       * @param bool $default - The default for the flag
    1589       */
    1590      public function set_enabled_flag_options($enabled, $default) {
    1591          $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin'));
    1592      }
    1593  
    1594      /**
    1595       * Set the advanced options flag on this admin setting.
    1596       *
    1597       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
    1598       * @param bool $default - The default for the flag
    1599       */
    1600      public function set_advanced_flag_options($enabled, $default) {
    1601          $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced'));
    1602      }
    1603  
    1604  
    1605      /**
    1606       * Set the locked options flag on this admin setting.
    1607       *
    1608       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
    1609       * @param bool $default - The default for the flag
    1610       */
    1611      public function set_locked_flag_options($enabled, $default) {
    1612          $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
    1613      }
    1614  
    1615      /**
    1616       * Get the currently saved value for a setting flag
    1617       *
    1618       * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting.
    1619       * @return bool
    1620       */
    1621      public function get_setting_flag_value(admin_setting_flag $flag) {
    1622          $value = $this->config_read($this->name . '_' . $flag->get_shortname());
    1623          if (!isset($value)) {
    1624              $value = $flag->get_default();
    1625          }
    1626  
    1627          return !empty($value);
    1628      }
    1629  
    1630      /**
    1631       * Get the list of defaults for the flags on this setting.
    1632       *
    1633       * @param array of strings describing the defaults for this setting. This is appended to by this function.
    1634       */
    1635      public function get_setting_flag_defaults(& $defaults) {
    1636          foreach ($this->flags as $flag) {
    1637              if ($flag->is_enabled() && $flag->get_default()) {
    1638                  $defaults[] = $flag->get_displayname();
    1639              }
    1640          }
    1641      }
    1642  
    1643      /**
    1644       * Output the input fields for the advanced and locked flags on this setting.
    1645       *
    1646       * @param bool $adv - The current value of the advanced flag.
    1647       * @param bool $locked - The current value of the locked flag.
    1648       * @return string $output - The html for the flags.
    1649       */
    1650      public function output_setting_flags() {
    1651          $output = '';
    1652  
    1653          foreach ($this->flags as $flag) {
    1654              if ($flag->is_enabled()) {
    1655                  $output .= $flag->output_setting_flag($this);
    1656              }
    1657          }
    1658  
    1659          if (!empty($output)) {
    1660              return html_writer::tag('span', $output, array('class' => 'adminsettingsflags'));
    1661          }
    1662          return $output;
    1663      }
    1664  
    1665      /**
    1666       * Write the values of the flags for this admin setting.
    1667       *
    1668       * @param array $data - The data submitted from the form or null to set the default value for new installs.
    1669       * @return bool - true if successful.
    1670       */
    1671      public function write_setting_flags($data) {
    1672          $result = true;
    1673          foreach ($this->flags as $flag) {
    1674              $result = $result && $flag->write_setting_flag($this, $data);
    1675          }
    1676          return $result;
    1677      }
    1678  
    1679      /**
    1680       * Set up $this->name and potentially $this->plugin
    1681       *
    1682       * Set up $this->name and possibly $this->plugin based on whether $name looks
    1683       * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
    1684       * on the names, that is, output a developer debug warning if the name
    1685       * contains anything other than [a-zA-Z0-9_]+.
    1686       *
    1687       * @param string $name the setting name passed in to the constructor.
    1688       */
    1689      private function parse_setting_name($name) {
    1690          $bits = explode('/', $name);
    1691          if (count($bits) > 2) {
    1692              throw new moodle_exception('invalidadminsettingname', '', '', $name);
    1693          }
    1694          $this->name = array_pop($bits);
    1695          if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
    1696              throw new moodle_exception('invalidadminsettingname', '', '', $name);
    1697          }
    1698          if (!empty($bits)) {
    1699              $this->plugin = array_pop($bits);
    1700              if ($this->plugin === 'moodle') {
    1701                  $this->plugin = null;
    1702              } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
    1703                      throw new moodle_exception('invalidadminsettingname', '', '', $name);
    1704                  }
    1705          }
    1706      }
    1707  
    1708      /**
    1709       * Returns the fullname prefixed by the plugin
    1710       * @return string
    1711       */
    1712      public function get_full_name() {
    1713          return 's_'.$this->plugin.'_'.$this->name;
    1714      }
    1715  
    1716      /**
    1717       * Returns the ID string based on plugin and name
    1718       * @return string
    1719       */
    1720      public function get_id() {
    1721          return 'id_s_'.$this->plugin.'_'.$this->name;
    1722      }
    1723  
    1724      /**
    1725       * @param bool $affectsmodinfo If true, changes to this setting will
    1726       *   cause the course cache to be rebuilt
    1727       */
    1728      public function set_affects_modinfo($affectsmodinfo) {
    1729          $this->affectsmodinfo = $affectsmodinfo;
    1730      }
    1731  
    1732      /**
    1733       * Returns the config if possible
    1734       *
    1735       * @return mixed returns config if successful else null
    1736       */
    1737      public function config_read($name) {
    1738          global $CFG;
    1739          if (!empty($this->plugin)) {
    1740              $value = get_config($this->plugin, $name);
    1741              return $value === false ? NULL : $value;
    1742  
    1743          } else {
    1744              if (isset($CFG->$name)) {
    1745                  return $CFG->$name;
    1746              } else {
    1747                  return NULL;
    1748              }
    1749          }
    1750      }
    1751  
    1752      /**
    1753       * Used to set a config pair and log change
    1754       *
    1755       * @param string $name
    1756       * @param mixed $value Gets converted to string if not null
    1757       * @return bool Write setting to config table
    1758       */
    1759      public function config_write($name, $value) {
    1760          global $DB, $USER, $CFG;
    1761  
    1762          if ($this->nosave) {
    1763              return true;
    1764          }
    1765  
    1766          // make sure it is a real change
    1767          $oldvalue = get_config($this->plugin, $name);
    1768          $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
    1769          $value = is_null($value) ? null : (string)$value;
    1770  
    1771          if ($oldvalue === $value) {
    1772              return true;
    1773          }
    1774  
    1775          // store change
    1776          set_config($name, $value, $this->plugin);
    1777  
    1778          // Some admin settings affect course modinfo
    1779          if ($this->affectsmodinfo) {
    1780              // Clear course cache for all courses
    1781              rebuild_course_cache(0, true);
    1782          }
    1783  
    1784          $this->add_to_config_log($name, $oldvalue, $value);
    1785  
    1786          return true; // BC only
    1787      }
    1788  
    1789      /**
    1790       * Log config changes if necessary.
    1791       * @param string $name
    1792       * @param string $oldvalue
    1793       * @param string $value
    1794       */
    1795      protected function add_to_config_log($name, $oldvalue, $value) {
    1796          add_to_config_log($name, $oldvalue, $value, $this->plugin);
    1797      }
    1798  
    1799      /**
    1800       * Returns current value of this setting
    1801       * @return mixed array or string depending on instance, NULL means not set yet
    1802       */
    1803      public abstract function get_setting();
    1804  
    1805      /**
    1806       * Returns default setting if exists
    1807       * @return mixed array or string depending on instance; NULL means no default, user must supply
    1808       */
    1809      public function get_defaultsetting() {
    1810          $adminroot =  admin_get_root(false, false);
    1811          if (!empty($adminroot->custom_defaults)) {
    1812              $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
    1813              if (isset($adminroot->custom_defaults[$plugin])) {
    1814                  if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
    1815                      return $adminroot->custom_defaults[$plugin][$this->name];
    1816                  }
    1817              }
    1818          }
    1819          return $this->defaultsetting;
    1820      }
    1821  
    1822      /**
    1823       * Store new setting
    1824       *
    1825       * @param mixed $data string or array, must not be NULL
    1826       * @return string empty string if ok, string error message otherwise
    1827       */
    1828      public abstract function write_setting($data);
    1829  
    1830      /**
    1831       * Return part of form with setting
    1832       * This function should always be overwritten
    1833       *
    1834       * @param mixed $data array or string depending on setting
    1835       * @param string $query
    1836       * @return string
    1837       */
    1838      public function output_html($data, $query='') {
    1839      // should be overridden
    1840          return;
    1841      }
    1842  
    1843      /**
    1844       * Function called if setting updated - cleanup, cache reset, etc.
    1845       * @param string $functionname Sets the function name
    1846       * @return void
    1847       */
    1848      public function set_updatedcallback($functionname) {
    1849          $this->updatedcallback = $functionname;
    1850      }
    1851  
    1852      /**
    1853       * Execute postupdatecallback if necessary.
    1854       * @param mixed $original original value before write_setting()
    1855       * @return bool true if changed, false if not.
    1856       */
    1857      public function post_write_settings($original) {
    1858          // Comparison must work for arrays too.
    1859          if (serialize($original) === serialize($this->get_setting())) {
    1860              return false;
    1861          }
    1862  
    1863          $callbackfunction = $this->updatedcallback;
    1864          if (!empty($callbackfunction) and function_exists($callbackfunction)) {
    1865              $callbackfunction($this->get_full_name());
    1866          }
    1867          return true;
    1868      }
    1869  
    1870      /**
    1871       * Is setting related to query text - used when searching
    1872       * @param string $query
    1873       * @return bool
    1874       */
    1875      public function is_related($query) {
    1876          if (strpos(strtolower($this->name), $query) !== false) {
    1877              return true;
    1878          }
    1879          if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
    1880              return true;
    1881          }
    1882          if (strpos(core_text::strtolower($this->description), $query) !== false) {
    1883              return true;
    1884          }
    1885          $current = $this->get_setting();
    1886          if (!is_null($current)) {
    1887              if (is_string($current)) {
    1888                  if (strpos(core_text::strtolower($current), $query) !== false) {
    1889                      return true;
    1890                  }
    1891              }
    1892          }
    1893          $default = $this->get_defaultsetting();
    1894          if (!is_null($default)) {
    1895              if (is_string($default)) {
    1896                  if (strpos(core_text::strtolower($default), $query) !== false) {
    1897                      return true;
    1898                  }
    1899              }
    1900          }
    1901          return false;
    1902      }
    1903  }
    1904  
    1905  /**
    1906   * An additional option that can be applied to an admin setting.
    1907   * The currently supported options are 'ADVANCED' and 'LOCKED'.
    1908   *
    1909   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1910   */
    1911  class admin_setting_flag {
    1912      /** @var bool Flag to indicate if this option can be toggled for this setting */
    1913      private $enabled = false;
    1914      /** @var bool Flag to indicate if this option defaults to true or false */
    1915      private $default = false;
    1916      /** @var string Short string used to create setting name - e.g. 'adv' */
    1917      private $shortname = '';
    1918      /** @var string String used as the label for this flag */
    1919      private $displayname = '';
    1920      /** @const Checkbox for this flag is displayed in admin page */
    1921      const ENABLED = true;
    1922      /** @const Checkbox for this flag is not displayed in admin page */
    1923      const DISABLED = false;
    1924  
    1925      /**
    1926       * Constructor
    1927       *
    1928       * @param bool $enabled Can this option can be toggled.
    1929       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
    1930       * @param bool $default The default checked state for this setting option.
    1931       * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
    1932       * @param string $displayname The displayname of this flag. Used as a label for the flag.
    1933       */
    1934      public function __construct($enabled, $default, $shortname, $displayname) {
    1935          $this->shortname = $shortname;
    1936          $this->displayname = $displayname;
    1937          $this->set_options($enabled, $default);
    1938      }
    1939  
    1940      /**
    1941       * Update the values of this setting options class
    1942       *
    1943       * @param bool $enabled Can this option can be toggled.
    1944       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
    1945       * @param bool $default The default checked state for this setting option.
    1946       */
    1947      public function set_options($enabled, $default) {
    1948          $this->enabled = $enabled;
    1949          $this->default = $default;
    1950      }
    1951  
    1952      /**
    1953       * Should this option appear in the interface and be toggleable?
    1954       *
    1955       * @return bool Is it enabled?
    1956       */
    1957      public function is_enabled() {
    1958          return $this->enabled;
    1959      }
    1960  
    1961      /**
    1962       * Should this option be checked by default?
    1963       *
    1964       * @return bool Is it on by default?
    1965       */
    1966      public function get_default() {
    1967          return $this->default;
    1968      }
    1969  
    1970      /**
    1971       * Return the short name for this flag. e.g. 'adv' or 'locked'
    1972       *
    1973       * @return string
    1974       */
    1975      public function get_shortname() {
    1976          return $this->shortname;
    1977      }
    1978  
    1979      /**
    1980       * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
    1981       *
    1982       * @return string
    1983       */
    1984      public function get_displayname() {
    1985          return $this->displayname;
    1986      }
    1987  
    1988      /**
    1989       * Save the submitted data for this flag - or set it to the default if $data is null.
    1990       *
    1991       * @param admin_setting $setting - The admin setting for this flag
    1992       * @param array $data - The data submitted from the form or null to set the default value for new installs.
    1993       * @return bool
    1994       */
    1995      public function write_setting_flag(admin_setting $setting, $data) {
    1996          $result = true;
    1997          if ($this->is_enabled()) {
    1998              if (!isset($data)) {
    1999                  $value = $this->get_default();
    2000              } else {
    2001                  $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
    2002              }
    2003              $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
    2004          }
    2005  
    2006          return $result;
    2007  
    2008      }
    2009  
    2010      /**
    2011       * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
    2012       *
    2013       * @param admin_setting $setting - The admin setting for this flag
    2014       * @return string - The html for the checkbox.
    2015       */
    2016      public function output_setting_flag(admin_setting $setting) {
    2017          $value = $setting->get_setting_flag_value($this);
    2018          $output = ' <input type="checkbox" class="form-checkbox" ' .
    2019                          ' id="' .  $setting->get_id() . '_' . $this->get_shortname() . '" ' .
    2020                          ' name="' . $setting->get_full_name() .  '_' . $this->get_shortname() . '" ' .
    2021                          ' value="1" ' . ($value ? 'checked="checked"' : '') . ' />' .
    2022                          ' <label for="' . $setting->get_id() . '_' . $this->get_shortname() . '">' .
    2023                          $this->get_displayname() .
    2024                          ' </label> ';
    2025          return $output;
    2026      }
    2027  }
    2028  
    2029  
    2030  /**
    2031   * No setting - just heading and text.
    2032   *
    2033   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2034   */
    2035  class admin_setting_heading extends admin_setting {
    2036  
    2037      /**
    2038       * not a setting, just text
    2039       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    2040       * @param string $heading heading
    2041       * @param string $information text in box
    2042       */
    2043      public function __construct($name, $heading, $information) {
    2044          $this->nosave = true;
    2045          parent::__construct($name, $heading, $information, '');
    2046      }
    2047  
    2048      /**
    2049       * Always returns true
    2050       * @return bool Always returns true
    2051       */
    2052      public function get_setting() {
    2053          return true;
    2054      }
    2055  
    2056      /**
    2057       * Always returns true
    2058       * @return bool Always returns true
    2059       */
    2060      public function get_defaultsetting() {
    2061          return true;
    2062      }
    2063  
    2064      /**
    2065       * Never write settings
    2066       * @return string Always returns an empty string
    2067       */
    2068      public function write_setting($data) {
    2069      // do not write any setting
    2070          return '';
    2071      }
    2072  
    2073      /**
    2074       * Returns an HTML string
    2075       * @return string Returns an HTML string
    2076       */
    2077      public function output_html($data, $query='') {
    2078          global $OUTPUT;
    2079          $return = '';
    2080          if ($this->visiblename != '') {
    2081              $return .= $OUTPUT->heading($this->visiblename, 3, 'main');
    2082          }
    2083          if ($this->description != '') {
    2084              $return .= $OUTPUT->box(highlight($query, markdown_to_html($this->description)), 'generalbox formsettingheading');
    2085          }
    2086          return $return;
    2087      }
    2088  }
    2089  
    2090  
    2091  /**
    2092   * The most flexibly setting, user is typing text
    2093   *
    2094   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2095   */
    2096  class admin_setting_configtext extends admin_setting {
    2097  
    2098      /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
    2099      public $paramtype;
    2100      /** @var int default field size */
    2101      public $size;
    2102  
    2103      /**
    2104       * Config text constructor
    2105       *
    2106       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    2107       * @param string $visiblename localised
    2108       * @param string $description long localised info
    2109       * @param string $defaultsetting
    2110       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
    2111       * @param int $size default field size
    2112       */
    2113      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
    2114          $this->paramtype = $paramtype;
    2115          if (!is_null($size)) {
    2116              $this->size  = $size;
    2117          } else {
    2118              $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
    2119          }
    2120          parent::__construct($name, $visiblename, $description, $defaultsetting);
    2121      }
    2122  
    2123      /**
    2124       * Return the setting
    2125       *
    2126       * @return mixed returns config if successful else null
    2127       */
    2128      public function get_setting() {
    2129          return $this->config_read($this->name);
    2130      }
    2131  
    2132      public function write_setting($data) {
    2133          if ($this->paramtype === PARAM_INT and $data === '') {
    2134          // do not complain if '' used instead of 0
    2135              $data = 0;
    2136          }
    2137          // $data is a string
    2138          $validated = $this->validate($data);
    2139          if ($validated !== true) {
    2140              return $validated;
    2141          }
    2142          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
    2143      }
    2144  
    2145      /**
    2146       * Validate data before storage
    2147       * @param string data
    2148       * @return mixed true if ok string if error found
    2149       */
    2150      public function validate($data) {
    2151          // allow paramtype to be a custom regex if it is the form of /pattern/
    2152          if (preg_match('#^/.*/$#', $this->paramtype)) {
    2153              if (preg_match($this->paramtype, $data)) {
    2154                  return true;
    2155              } else {
    2156                  return get_string('validateerror', 'admin');
    2157              }
    2158  
    2159          } else if ($this->paramtype === PARAM_RAW) {
    2160              return true;
    2161  
    2162          } else {
    2163              $cleaned = clean_param($data, $this->paramtype);
    2164              if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
    2165                  return true;
    2166              } else {
    2167                  return get_string('validateerror', 'admin');
    2168              }
    2169          }
    2170      }
    2171  
    2172      /**
    2173       * Return an XHTML string for the setting
    2174       * @return string Returns an XHTML string
    2175       */
    2176      public function output_html($data, $query='') {
    2177          $default = $this->get_defaultsetting();
    2178  
    2179          return format_admin_setting($this, $this->visiblename,
    2180          '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
    2181          $this->description, true, '', $default, $query);
    2182      }
    2183  }
    2184  
    2185  /**
    2186   * Text input with a maximum length constraint.
    2187   *
    2188   * @copyright 2015 onwards Ankit Agarwal
    2189   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2190   */
    2191  class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
    2192  
    2193      /** @var int maximum number of chars allowed. */
    2194      protected $maxlength;
    2195  
    2196      /**
    2197       * Config text constructor
    2198       *
    2199       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
    2200       *                     or 'myplugin/mysetting' for ones in config_plugins.
    2201       * @param string $visiblename localised
    2202       * @param string $description long localised info
    2203       * @param string $defaultsetting
    2204       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
    2205       * @param int $size default field size
    2206       * @param mixed $maxlength int maxlength allowed, 0 for infinite.
    2207       */
    2208      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW,
    2209                                  $size=null, $maxlength = 0) {
    2210          $this->maxlength = $maxlength;
    2211          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
    2212      }
    2213  
    2214      /**
    2215       * Validate data before storage
    2216       *
    2217       * @param string $data data
    2218       * @return mixed true if ok string if error found
    2219       */
    2220      public function validate($data) {
    2221          $parentvalidation = parent::validate($data);
    2222          if ($parentvalidation === true) {
    2223              if ($this->maxlength > 0) {
    2224                  // Max length check.
    2225                  $length = core_text::strlen($data);
    2226                  if ($length > $this->maxlength) {
    2227                      return get_string('maximumchars', 'moodle',  $this->maxlength);
    2228                  }
    2229                  return true;
    2230              } else {
    2231                  return true; // No max length check needed.
    2232              }
    2233          } else {
    2234              return $parentvalidation;
    2235          }
    2236      }
    2237  }
    2238  
    2239  /**
    2240   * General text area without html editor.
    2241   *
    2242   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2243   */
    2244  class admin_setting_configtextarea extends admin_setting_configtext {
    2245      private $rows;
    2246      private $cols;
    2247  
    2248      /**
    2249       * @param string $name
    2250       * @param string $visiblename
    2251       * @param string $description
    2252       * @param mixed $defaultsetting string or array
    2253       * @param mixed $paramtype
    2254       * @param string $cols The number of columns to make the editor
    2255       * @param string $rows The number of rows to make the editor
    2256       */
    2257      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
    2258          $this->rows = $rows;
    2259          $this->cols = $cols;
    2260          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
    2261      }
    2262  
    2263      /**
    2264       * Returns an XHTML string for the editor
    2265       *
    2266       * @param string $data
    2267       * @param string $query
    2268       * @return string XHTML string for the editor
    2269       */
    2270      public function output_html($data, $query='') {
    2271          $default = $this->get_defaultsetting();
    2272  
    2273          $defaultinfo = $default;
    2274          if (!is_null($default) and $default !== '') {
    2275              $defaultinfo = "\n".$default;
    2276          }
    2277  
    2278          return format_admin_setting($this, $this->visiblename,
    2279          '<div class="form-textarea" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'" spellcheck="true">'. s($data) .'</textarea></div>',
    2280          $this->description, true, '', $defaultinfo, $query);
    2281      }
    2282  }
    2283  
    2284  
    2285  /**
    2286   * General text area with html editor.
    2287   */
    2288  class admin_setting_confightmleditor extends admin_setting_configtext {
    2289      private $rows;
    2290      private $cols;
    2291  
    2292      /**
    2293       * @param string $name
    2294       * @param string $visiblename
    2295       * @param string $description
    2296       * @param mixed $defaultsetting string or array
    2297       * @param mixed $paramtype
    2298       */
    2299      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
    2300          $this->rows = $rows;
    2301          $this->cols = $cols;
    2302          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
    2303          editors_head_setup();
    2304      }
    2305  
    2306      /**
    2307       * Returns an XHTML string for the editor
    2308       *
    2309       * @param string $data
    2310       * @param string $query
    2311       * @return string XHTML string for the editor
    2312       */
    2313      public function output_html($data, $query='') {
    2314          $default = $this->get_defaultsetting();
    2315  
    2316          $defaultinfo = $default;
    2317          if (!is_null($default) and $default !== '') {
    2318              $defaultinfo = "\n".$default;
    2319          }
    2320  
    2321          $editor = editors_get_preferred_editor(FORMAT_HTML);
    2322          $editor->set_text($data);
    2323          $editor->use_editor($this->get_id(), array('noclean'=>true));
    2324  
    2325          return format_admin_setting($this, $this->visiblename,
    2326          '<div class="form-textarea"><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'" spellcheck="true">'. s($data) .'</textarea></div>',
    2327          $this->description, true, '', $defaultinfo, $query);
    2328      }
    2329  }
    2330  
    2331  
    2332  /**
    2333   * Password field, allows unmasking of password
    2334   *
    2335   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2336   */
    2337  class admin_setting_configpasswordunmask extends admin_setting_configtext {
    2338      /**
    2339       * Constructor
    2340       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    2341       * @param string $visiblename localised
    2342       * @param string $description long localised info
    2343       * @param string $defaultsetting default password
    2344       */
    2345      public function __construct($name, $visiblename, $description, $defaultsetting) {
    2346          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
    2347      }
    2348  
    2349      /**
    2350       * Log config changes if necessary.
    2351       * @param string $name
    2352       * @param string $oldvalue
    2353       * @param string $value
    2354       */
    2355      protected function add_to_config_log($name, $oldvalue, $value) {
    2356          if ($value !== '') {
    2357              $value = '********';
    2358          }
    2359          if ($oldvalue !== '' and $oldvalue !== null) {
    2360              $oldvalue = '********';
    2361          }
    2362          parent::add_to_config_log($name, $oldvalue, $value);
    2363      }
    2364  
    2365      /**
    2366       * Returns XHTML for the field
    2367       * Writes Javascript into the HTML below right before the last div
    2368       *
    2369       * @todo Make javascript available through newer methods if possible
    2370       * @param string $data Value for the field
    2371       * @param string $query Passed as final argument for format_admin_setting
    2372       * @return string XHTML field
    2373       */
    2374      public function output_html($data, $query='') {
    2375          $id = $this->get_id();
    2376          $unmask = get_string('unmaskpassword', 'form');
    2377          $unmaskjs = '<script type="text/javascript">
    2378  //<![CDATA[
    2379  var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
    2380  
    2381  document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
    2382  
    2383  var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
    2384  
    2385  var unmaskchb = document.createElement("input");
    2386  unmaskchb.setAttribute("type", "checkbox");
    2387  unmaskchb.setAttribute("id", "'.$id.'unmask");
    2388  unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
    2389  unmaskdiv.appendChild(unmaskchb);
    2390  
    2391  var unmasklbl = document.createElement("label");
    2392  unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
    2393  if (is_ie) {
    2394    unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
    2395  } else {
    2396    unmasklbl.setAttribute("for", "'.$id.'unmask");
    2397  }
    2398  unmaskdiv.appendChild(unmasklbl);
    2399  
    2400  if (is_ie) {
    2401    // ugly hack to work around the famous onchange IE bug
    2402    unmaskchb.onclick = function() {this.blur();};
    2403    unmaskdiv.onclick = function() {this.blur();};
    2404  }
    2405  //]]>
    2406  </script>';
    2407          return format_admin_setting($this, $this->visiblename,
    2408          '<div class="form-password"><input type="password" size="'.$this->size.'" id="'.$id.'" name="'.$this->get_full_name().'" value="'.s($data).'" /><div class="unmask" id="'.$id.'unmaskdiv"></div>'.$unmaskjs.'</div>',
    2409          $this->description, true, '', NULL, $query);
    2410      }
    2411  }
    2412  
    2413  /**
    2414   * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
    2415   * Note: Only advanced makes sense right now - locked does not.
    2416   *
    2417   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2418   */
    2419  class admin_setting_configempty extends admin_setting_configtext {
    2420  
    2421      /**
    2422       * @param string $name
    2423       * @param string $visiblename
    2424       * @param string $description
    2425       */
    2426      public function __construct($name, $visiblename, $description) {
    2427          parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
    2428      }
    2429  
    2430      /**
    2431       * Returns an XHTML string for the hidden field
    2432       *
    2433       * @param string $data
    2434       * @param string $query
    2435       * @return string XHTML string for the editor
    2436       */
    2437      public function output_html($data, $query='') {
    2438          return format_admin_setting($this,
    2439                                      $this->visiblename,
    2440                                      '<div class="form-empty" >' .
    2441                                      '<input type="hidden"' .
    2442                                          ' id="'. $this->get_id() .'"' .
    2443                                          ' name="'. $this->get_full_name() .'"' .
    2444                                          ' value=""/></div>',
    2445                                      $this->description,
    2446                                      true,
    2447                                      '',
    2448                                      get_string('none'),
    2449                                      $query);
    2450      }
    2451  }
    2452  
    2453  
    2454  /**
    2455   * Path to directory
    2456   *
    2457   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2458   */
    2459  class admin_setting_configfile extends admin_setting_configtext {
    2460      /**
    2461       * Constructor
    2462       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    2463       * @param string $visiblename localised
    2464       * @param string $description long localised info
    2465       * @param string $defaultdirectory default directory location
    2466       */
    2467      public function __construct($name, $visiblename, $description, $defaultdirectory) {
    2468          parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
    2469      }
    2470  
    2471      /**
    2472       * Returns XHTML for the field
    2473       *
    2474       * Returns XHTML for the field and also checks whether the file
    2475       * specified in $data exists using file_exists()
    2476       *
    2477       * @param string $data File name and path to use in value attr
    2478       * @param string $query
    2479       * @return string XHTML field
    2480       */
    2481      public function output_html($data, $query='') {
    2482          global $CFG;
    2483          $default = $this->get_defaultsetting();
    2484  
    2485          if ($data) {
    2486              if (file_exists($data)) {
    2487                  $executable = '<span class="pathok">&#x2714;</span>';
    2488              } else {
    2489                  $executable = '<span class="patherror">&#x2718;</span>';
    2490              }
    2491          } else {
    2492              $executable = '';
    2493          }
    2494          $readonly = '';
    2495          if (!empty($CFG->preventexecpath)) {
    2496              $this->visiblename .= '<div class="form-overridden">'.get_string('execpathnotallowed', 'admin').'</div>';
    2497              $readonly = 'readonly="readonly"';
    2498          }
    2499  
    2500          return format_admin_setting($this, $this->visiblename,
    2501          '<div class="form-file defaultsnext"><input '.$readonly.' type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
    2502          $this->description, true, '', $default, $query);
    2503      }
    2504  
    2505      /**
    2506       * Checks if execpatch has been disabled in config.php
    2507       */
    2508      public function write_setting($data) {
    2509          global $CFG;
    2510          if (!empty($CFG->preventexecpath)) {
    2511              if ($this->get_setting() === null) {
    2512                  // Use default during installation.
    2513                  $data = $this->get_defaultsetting();
    2514                  if ($data === null) {
    2515                      $data = '';
    2516                  }
    2517              } else {
    2518                  return '';
    2519              }
    2520          }
    2521          return parent::write_setting($data);
    2522      }
    2523  }
    2524  
    2525  
    2526  /**
    2527   * Path to executable file
    2528   *
    2529   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2530   */
    2531  class admin_setting_configexecutable extends admin_setting_configfile {
    2532  
    2533      /**
    2534       * Returns an XHTML field
    2535       *
    2536       * @param string $data This is the value for the field
    2537       * @param string $query
    2538       * @return string XHTML field
    2539       */
    2540      public function output_html($data, $query='') {
    2541          global $CFG;
    2542          $default = $this->get_defaultsetting();
    2543  
    2544          if ($data) {
    2545              if (file_exists($data) and !is_dir($data) and is_executable($data)) {
    2546                  $executable = '<span class="pathok">&#x2714;</span>';
    2547              } else {
    2548                  $executable = '<span class="patherror">&#x2718;</span>';
    2549              }
    2550          } else {
    2551              $executable = '';
    2552          }
    2553          $readonly = '';
    2554          if (!empty($CFG->preventexecpath)) {
    2555              $this->visiblename .= '<div class="form-overridden">'.get_string('execpathnotallowed', 'admin').'</div>';
    2556              $readonly = 'readonly="readonly"';
    2557          }
    2558  
    2559          return format_admin_setting($this, $this->visiblename,
    2560          '<div class="form-file defaultsnext"><input '.$readonly.' type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
    2561          $this->description, true, '', $default, $query);
    2562      }
    2563  }
    2564  
    2565  
    2566  /**
    2567   * Path to directory
    2568   *
    2569   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2570   */
    2571  class admin_setting_configdirectory extends admin_setting_configfile {
    2572  
    2573      /**
    2574       * Returns an XHTML field
    2575       *
    2576       * @param string $data This is the value for the field
    2577       * @param string $query
    2578       * @return string XHTML
    2579       */
    2580      public function output_html($data, $query='') {
    2581          global $CFG;
    2582          $default = $this->get_defaultsetting();
    2583  
    2584          if ($data) {
    2585              if (file_exists($data) and is_dir($data)) {
    2586                  $executable = '<span class="pathok">&#x2714;</span>';
    2587              } else {
    2588                  $executable = '<span class="patherror">&#x2718;</span>';
    2589              }
    2590          } else {
    2591              $executable = '';
    2592          }
    2593          $readonly = '';
    2594          if (!empty($CFG->preventexecpath)) {
    2595              $this->visiblename .= '<div class="form-overridden">'.get_string('execpathnotallowed', 'admin').'</div>';
    2596              $readonly = 'readonly="readonly"';
    2597          }
    2598  
    2599          return format_admin_setting($this, $this->visiblename,
    2600          '<div class="form-file defaultsnext"><input '.$readonly.' type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
    2601          $this->description, true, '', $default, $query);
    2602      }
    2603  }
    2604  
    2605  
    2606  /**
    2607   * Checkbox
    2608   *
    2609   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2610   */
    2611  class admin_setting_configcheckbox extends admin_setting {
    2612      /** @var string Value used when checked */
    2613      public $yes;
    2614      /** @var string Value used when not checked */
    2615      public $no;
    2616  
    2617      /**
    2618       * Constructor
    2619       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    2620       * @param string $visiblename localised
    2621       * @param string $description long localised info
    2622       * @param string $defaultsetting
    2623       * @param string $yes value used when checked
    2624       * @param string $no value used when not checked
    2625       */
    2626      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
    2627          parent::__construct($name, $visiblename, $description, $defaultsetting);
    2628          $this->yes = (string)$yes;
    2629          $this->no  = (string)$no;
    2630      }
    2631  
    2632      /**
    2633       * Retrieves the current setting using the objects name
    2634       *
    2635       * @return string
    2636       */
    2637      public function get_setting() {
    2638          return $this->config_read($this->name);
    2639      }
    2640  
    2641      /**
    2642       * Sets the value for the setting
    2643       *
    2644       * Sets the value for the setting to either the yes or no values
    2645       * of the object by comparing $data to yes
    2646       *
    2647       * @param mixed $data Gets converted to str for comparison against yes value
    2648       * @return string empty string or error
    2649       */
    2650      public function write_setting($data) {
    2651          if ((string)$data === $this->yes) { // convert to strings before comparison
    2652              $data = $this->yes;
    2653          } else {
    2654              $data = $this->no;
    2655          }
    2656          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
    2657      }
    2658  
    2659      /**
    2660       * Returns an XHTML checkbox field
    2661       *
    2662       * @param string $data If $data matches yes then checkbox is checked
    2663       * @param string $query
    2664       * @return string XHTML field
    2665       */
    2666      public function output_html($data, $query='') {
    2667          $default = $this->get_defaultsetting();
    2668  
    2669          if (!is_null($default)) {
    2670              if ((string)$default === $this->yes) {
    2671                  $defaultinfo = get_string('checkboxyes', 'admin');
    2672              } else {
    2673                  $defaultinfo = get_string('checkboxno', 'admin');
    2674              }
    2675          } else {
    2676              $defaultinfo = NULL;
    2677          }
    2678  
    2679          if ((string)$data === $this->yes) { // convert to strings before comparison
    2680              $checked = 'checked="checked"';
    2681          } else {
    2682              $checked = '';
    2683          }
    2684  
    2685          return format_admin_setting($this, $this->visiblename,
    2686          '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
    2687              .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
    2688          $this->description, true, '', $defaultinfo, $query);
    2689      }
    2690  }
    2691  
    2692  
    2693  /**
    2694   * Multiple checkboxes, each represents different value, stored in csv format
    2695   *
    2696   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2697   */
    2698  class admin_setting_configmulticheckbox extends admin_setting {
    2699      /** @var array Array of choices value=>label */
    2700      public $choices;
    2701  
    2702      /**
    2703       * Constructor: uses parent::__construct
    2704       *
    2705       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    2706       * @param string $visiblename localised
    2707       * @param string $description long localised info
    2708       * @param array $defaultsetting array of selected
    2709       * @param array $choices array of $value=>$label for each checkbox
    2710       */
    2711      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
    2712          $this->choices = $choices;
    2713          parent::__construct($name, $visiblename, $description, $defaultsetting);
    2714      }
    2715  
    2716      /**
    2717       * This public function may be used in ancestors for lazy loading of choices
    2718       *
    2719       * @todo Check if this function is still required content commented out only returns true
    2720       * @return bool true if loaded, false if error
    2721       */
    2722      public function load_choices() {
    2723          /*
    2724          if (is_array($this->choices)) {
    2725              return true;
    2726          }
    2727          .... load choices here
    2728          */
    2729          return true;
    2730      }
    2731  
    2732      /**
    2733       * Is setting related to query text - used when searching
    2734       *
    2735       * @param string $query
    2736       * @return bool true on related, false on not or failure
    2737       */
    2738      public function is_related($query) {
    2739          if (!$this->load_choices() or empty($this->choices)) {
    2740              return false;
    2741          }
    2742          if (parent::is_related($query)) {
    2743              return true;
    2744          }
    2745  
    2746          foreach ($this->choices as $desc) {
    2747              if (strpos(core_text::strtolower($desc), $query) !== false) {
    2748                  return true;
    2749              }
    2750          }
    2751          return false;
    2752      }
    2753  
    2754      /**
    2755       * Returns the current setting if it is set
    2756       *
    2757       * @return mixed null if null, else an array
    2758       */
    2759      public function get_setting() {
    2760          $result = $this->config_read($this->name);
    2761  
    2762          if (is_null($result)) {
    2763              return NULL;
    2764          }
    2765          if ($result === '') {
    2766              return array();
    2767          }
    2768          $enabled = explode(',', $result);
    2769          $setting = array();
    2770          foreach ($enabled as $option) {
    2771              $setting[$option] = 1;
    2772          }
    2773          return $setting;
    2774      }
    2775  
    2776      /**
    2777       * Saves the setting(s) provided in $data
    2778       *
    2779       * @param array $data An array of data, if not array returns empty str
    2780       * @return mixed empty string on useless data or bool true=success, false=failed
    2781       */
    2782      public function write_setting($data) {
    2783          if (!is_array($data)) {
    2784              return ''; // ignore it
    2785          }
    2786          if (!$this->load_choices() or empty($this->choices)) {
    2787              return '';
    2788          }
    2789          unset($data['xxxxx']);
    2790          $result = array();
    2791          foreach ($data as $key => $value) {
    2792              if ($value and array_key_exists($key, $this->choices)) {
    2793                  $result[] = $key;
    2794              }
    2795          }
    2796          return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
    2797      }
    2798  
    2799      /**
    2800       * Returns XHTML field(s) as required by choices
    2801       *
    2802       * Relies on data being an array should data ever be another valid vartype with
    2803       * acceptable value this may cause a warning/error
    2804       * if (!is_array($data)) would fix the problem
    2805       *
    2806       * @todo Add vartype handling to ensure $data is an array
    2807       *
    2808       * @param array $data An array of checked values
    2809       * @param string $query
    2810       * @return string XHTML field
    2811       */
    2812      public function output_html($data, $query='') {
    2813          if (!$this->load_choices() or empty($this->choices)) {
    2814              return '';
    2815          }
    2816          $default = $this->get_defaultsetting();
    2817          if (is_null($default)) {
    2818              $default = array();
    2819          }
    2820          if (is_null($data)) {
    2821              $data = array();
    2822          }
    2823          $options = array();
    2824          $defaults = array();
    2825          foreach ($this->choices as $key=>$description) {
    2826              if (!empty($data[$key])) {
    2827                  $checked = 'checked="checked"';
    2828              } else {
    2829                  $checked = '';
    2830              }
    2831              if (!empty($default[$key])) {
    2832                  $defaults[] = $description;
    2833              }
    2834  
    2835              $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
    2836                  .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
    2837          }
    2838  
    2839          if (is_null($default)) {
    2840              $defaultinfo = NULL;
    2841          } else if (!empty($defaults)) {
    2842                  $defaultinfo = implode(', ', $defaults);
    2843              } else {
    2844                  $defaultinfo = get_string('none');
    2845              }
    2846  
    2847          $return = '<div class="form-multicheckbox">';
    2848          $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
    2849          if ($options) {
    2850              $return .= '<ul>';
    2851              foreach ($options as $option) {
    2852                  $return .= '<li>'.$option.'</li>';
    2853              }
    2854              $return .= '</ul>';
    2855          }
    2856          $return .= '</div>';
    2857  
    2858          return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
    2859  
    2860      }
    2861  }
    2862  
    2863  
    2864  /**
    2865   * Multiple checkboxes 2, value stored as string 00101011
    2866   *
    2867   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2868   */
    2869  class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
    2870  
    2871      /**
    2872       * Returns the setting if set
    2873       *
    2874       * @return mixed null if not set, else an array of set settings
    2875       */
    2876      public function get_setting() {
    2877          $result = $this->config_read($this->name);
    2878          if (is_null($result)) {
    2879              return NULL;
    2880          }
    2881          if (!$this->load_choices()) {
    2882              return NULL;
    2883          }
    2884          $result = str_pad($result, count($this->choices), '0');
    2885          $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
    2886          $setting = array();
    2887          foreach ($this->choices as $key=>$unused) {
    2888              $value = array_shift($result);
    2889              if ($value) {
    2890                  $setting[$key] = 1;
    2891              }
    2892          }
    2893          return $setting;
    2894      }
    2895  
    2896      /**
    2897       * Save setting(s) provided in $data param
    2898       *
    2899       * @param array $data An array of settings to save
    2900       * @return mixed empty string for bad data or bool true=>success, false=>error
    2901       */
    2902      public function write_setting($data) {
    2903          if (!is_array($data)) {
    2904              return ''; // ignore it
    2905          }
    2906          if (!$this->load_choices() or empty($this->choices)) {
    2907              return '';
    2908          }
    2909          $result = '';
    2910          foreach ($this->choices as $key=>$unused) {
    2911              if (!empty($data[$key])) {
    2912                  $result .= '1';
    2913              } else {
    2914                  $result .= '0';
    2915              }
    2916          }
    2917          return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
    2918      }
    2919  }
    2920  
    2921  
    2922  /**
    2923   * Select one value from list
    2924   *
    2925   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    2926   */
    2927  class admin_setting_configselect extends admin_setting {
    2928      /** @var array Array of choices value=>label */
    2929      public $choices;
    2930  
    2931      /**
    2932       * Constructor
    2933       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    2934       * @param string $visiblename localised
    2935       * @param string $description long localised info
    2936       * @param string|int $defaultsetting
    2937       * @param array $choices array of $value=>$label for each selection
    2938       */
    2939      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
    2940          $this->choices = $choices;
    2941          parent::__construct($name, $visiblename, $description, $defaultsetting);
    2942      }
    2943  
    2944      /**
    2945       * This function may be used in ancestors for lazy loading of choices
    2946       *
    2947       * Override this method if loading of choices is expensive, such
    2948       * as when it requires multiple db requests.
    2949       *
    2950       * @return bool true if loaded, false if error
    2951       */
    2952      public function load_choices() {
    2953          /*
    2954          if (is_array($this->choices)) {
    2955              return true;
    2956          }
    2957          .... load choices here
    2958          */
    2959          return true;
    2960      }
    2961  
    2962      /**
    2963       * Check if this is $query is related to a choice
    2964       *
    2965       * @param string $query
    2966       * @return bool true if related, false if not
    2967       */
    2968      public function is_related($query) {
    2969          if (parent::is_related($query)) {
    2970              return true;
    2971          }
    2972          if (!$this->load_choices()) {
    2973              return false;
    2974          }
    2975          foreach ($this->choices as $key=>$value) {
    2976              if (strpos(core_text::strtolower($key), $query) !== false) {
    2977                  return true;
    2978              }
    2979              if (strpos(core_text::strtolower($value), $query) !== false) {
    2980                  return true;
    2981              }
    2982          }
    2983          return false;
    2984      }
    2985  
    2986      /**
    2987       * Return the setting
    2988       *
    2989       * @return mixed returns config if successful else null
    2990       */
    2991      public function get_setting() {
    2992          return $this->config_read($this->name);
    2993      }
    2994  
    2995      /**
    2996       * Save a setting
    2997       *
    2998       * @param string $data
    2999       * @return string empty of error string
    3000       */
    3001      public function write_setting($data) {
    3002          if (!$this->load_choices() or empty($this->choices)) {
    3003              return '';
    3004          }
    3005          if (!array_key_exists($data, $this->choices)) {
    3006              return ''; // ignore it
    3007          }
    3008  
    3009          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
    3010      }
    3011  
    3012      /**
    3013       * Returns XHTML select field
    3014       *
    3015       * Ensure the options are loaded, and generate the XHTML for the select
    3016       * element and any warning message. Separating this out from output_html
    3017       * makes it easier to subclass this class.
    3018       *
    3019       * @param string $data the option to show as selected.
    3020       * @param string $current the currently selected option in the database, null if none.
    3021       * @param string $default the default selected option.
    3022       * @return array the HTML for the select element, and a warning message.
    3023       */
    3024      public function output_select_html($data, $current, $default, $extraname = '') {
    3025          if (!$this->load_choices() or empty($this->choices)) {
    3026              return array('', '');
    3027          }
    3028  
    3029          $warning = '';
    3030          if (is_null($current)) {
    3031          // first run
    3032          } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
    3033              // no warning
    3034              } else if (!array_key_exists($current, $this->choices)) {
    3035                      $warning = get_string('warningcurrentsetting', 'admin', s($current));
    3036                      if (!is_null($default) and $data == $current) {
    3037                          $data = $default; // use default instead of first value when showing the form
    3038                      }
    3039                  }
    3040  
    3041          $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
    3042          foreach ($this->choices as $key => $value) {
    3043          // the string cast is needed because key may be integer - 0 is equal to most strings!
    3044              $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
    3045          }
    3046          $selecthtml .= '</select>';
    3047          return array($selecthtml, $warning);
    3048      }
    3049  
    3050      /**
    3051       * Returns XHTML select field and wrapping div(s)
    3052       *
    3053       * @see output_select_html()
    3054       *
    3055       * @param string $data the option to show as selected
    3056       * @param string $query
    3057       * @return string XHTML field and wrapping div
    3058       */
    3059      public function output_html($data, $query='') {
    3060          $default = $this->get_defaultsetting();
    3061          $current = $this->get_setting();
    3062  
    3063          list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
    3064          if (!$selecthtml) {
    3065              return '';
    3066          }
    3067  
    3068          if (!is_null($default) and array_key_exists($default, $this->choices)) {
    3069              $defaultinfo = $this->choices[$default];
    3070          } else {
    3071              $defaultinfo = NULL;
    3072          }
    3073  
    3074          $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
    3075  
    3076          return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
    3077      }
    3078  }
    3079  
    3080  
    3081  /**
    3082   * Select multiple items from list
    3083   *
    3084   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3085   */
    3086  class admin_setting_configmultiselect extends admin_setting_configselect {
    3087      /**
    3088       * Constructor
    3089       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    3090       * @param string $visiblename localised
    3091       * @param string $description long localised info
    3092       * @param array $defaultsetting array of selected items
    3093       * @param array $choices array of $value=>$label for each list item
    3094       */
    3095      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
    3096          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
    3097      }
    3098  
    3099      /**
    3100       * Returns the select setting(s)
    3101       *
    3102       * @return mixed null or array. Null if no settings else array of setting(s)
    3103       */
    3104      public function get_setting() {
    3105          $result = $this->config_read($this->name);
    3106          if (is_null($result)) {
    3107              return NULL;
    3108          }
    3109          if ($result === '') {
    3110              return array();
    3111          }
    3112          return explode(',', $result);
    3113      }
    3114  
    3115      /**
    3116       * Saves setting(s) provided through $data
    3117       *
    3118       * Potential bug in the works should anyone call with this function
    3119       * using a vartype that is not an array
    3120       *
    3121       * @param array $data
    3122       */
    3123      public function write_setting($data) {
    3124          if (!is_array($data)) {
    3125              return ''; //ignore it
    3126          }
    3127          if (!$this->load_choices() or empty($this->choices)) {
    3128              return '';
    3129          }
    3130  
    3131          unset($data['xxxxx']);
    3132  
    3133          $save = array();
    3134          foreach ($data as $value) {
    3135              if (!array_key_exists($value, $this->choices)) {
    3136                  continue; // ignore it
    3137              }
    3138              $save[] = $value;
    3139          }
    3140  
    3141          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
    3142      }
    3143  
    3144      /**
    3145       * Is setting related to query text - used when searching
    3146       *
    3147       * @param string $query
    3148       * @return bool true if related, false if not
    3149       */
    3150      public function is_related($query) {
    3151          if (!$this->load_choices() or empty($this->choices)) {
    3152              return false;
    3153          }
    3154          if (parent::is_related($query)) {
    3155              return true;
    3156          }
    3157  
    3158          foreach ($this->choices as $desc) {
    3159              if (strpos(core_text::strtolower($desc), $query) !== false) {
    3160                  return true;
    3161              }
    3162          }
    3163          return false;
    3164      }
    3165  
    3166      /**
    3167       * Returns XHTML multi-select field
    3168       *
    3169       * @todo Add vartype handling to ensure $data is an array
    3170       * @param array $data Array of values to select by default
    3171       * @param string $query
    3172       * @return string XHTML multi-select field
    3173       */
    3174      public function output_html($data, $query='') {
    3175          if (!$this->load_choices() or empty($this->choices)) {
    3176              return '';
    3177          }
    3178          $choices = $this->choices;
    3179          $default = $this->get_defaultsetting();
    3180          if (is_null($default)) {
    3181              $default = array();
    3182          }
    3183          if (is_null($data)) {
    3184              $data = array();
    3185          }
    3186  
    3187          $defaults = array();
    3188          $size = min(10, count($this->choices));
    3189          $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
    3190          $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
    3191          foreach ($this->choices as $key => $description) {
    3192              if (in_array($key, $data)) {
    3193                  $selected = 'selected="selected"';
    3194              } else {
    3195                  $selected = '';
    3196              }
    3197              if (in_array($key, $default)) {
    3198                  $defaults[] = $description;
    3199              }
    3200  
    3201              $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
    3202          }
    3203  
    3204          if (is_null($default)) {
    3205              $defaultinfo = NULL;
    3206          } if (!empty($defaults)) {
    3207              $defaultinfo = implode(', ', $defaults);
    3208          } else {
    3209              $defaultinfo = get_string('none');
    3210          }
    3211  
    3212          $return .= '</select></div>';
    3213          return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
    3214      }
    3215  }
    3216  
    3217  /**
    3218   * Time selector
    3219   *
    3220   * This is a liiitle bit messy. we're using two selects, but we're returning
    3221   * them as an array named after $name (so we only use $name2 internally for the setting)
    3222   *
    3223   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3224   */
    3225  class admin_setting_configtime extends admin_setting {
    3226      /** @var string Used for setting second select (minutes) */
    3227      public $name2;
    3228  
    3229      /**
    3230       * Constructor
    3231       * @param string $hoursname setting for hours
    3232       * @param string $minutesname setting for hours
    3233       * @param string $visiblename localised
    3234       * @param string $description long localised info
    3235       * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
    3236       */
    3237      public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
    3238          $this->name2 = $minutesname;
    3239          parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
    3240      }
    3241  
    3242      /**
    3243       * Get the selected time
    3244       *
    3245       * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
    3246       */
    3247      public function get_setting() {
    3248          $result1 = $this->config_read($this->name);
    3249          $result2 = $this->config_read($this->name2);
    3250          if (is_null($result1) or is_null($result2)) {
    3251              return NULL;
    3252          }
    3253  
    3254          return array('h' => $result1, 'm' => $result2);
    3255      }
    3256  
    3257      /**
    3258       * Store the time (hours and minutes)
    3259       *
    3260       * @param array $data Must be form 'h'=>xx, 'm'=>xx
    3261       * @return bool true if success, false if not
    3262       */
    3263      public function write_setting($data) {
    3264          if (!is_array($data)) {
    3265              return '';
    3266          }
    3267  
    3268          $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
    3269          return ($result ? '' : get_string('errorsetting', 'admin'));
    3270      }
    3271  
    3272      /**
    3273       * Returns XHTML time select fields
    3274       *
    3275       * @param array $data Must be form 'h'=>xx, 'm'=>xx
    3276       * @param string $query
    3277       * @return string XHTML time select fields and wrapping div(s)
    3278       */
    3279      public function output_html($data, $query='') {
    3280          $default = $this->get_defaultsetting();
    3281  
    3282          if (is_array($default)) {
    3283              $defaultinfo = $default['h'].':'.$default['m'];
    3284          } else {
    3285              $defaultinfo = NULL;
    3286          }
    3287  
    3288          $return  = '<div class="form-time defaultsnext">';
    3289          $return .= '<label class="accesshide" for="' . $this->get_id() . 'h">' . get_string('hours') . '</label>';
    3290          $return .= '<select id="' . $this->get_id() . 'h" name="' . $this->get_full_name() . '[h]">';
    3291          for ($i = 0; $i < 24; $i++) {
    3292              $return .= '<option value="' . $i . '"' . ($i == $data['h'] ? ' selected="selected"' : '') . '>' . $i . '</option>';
    3293          }
    3294          $return .= '</select>:';
    3295          $return .= '<label class="accesshide" for="' . $this->get_id() . 'm">' . get_string('minutes') . '</label>';
    3296          $return .= '<select id="' . $this->get_id() . 'm" name="' . $this->get_full_name() . '[m]">';
    3297          for ($i = 0; $i < 60; $i += 5) {
    3298              $return .= '<option value="' . $i . '"' . ($i == $data['m'] ? ' selected="selected"' : '') . '>' . $i . '</option>';
    3299          }
    3300          $return .= '</select>';
    3301          $return .= '</div>';
    3302          return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
    3303      }
    3304  
    3305  }
    3306  
    3307  
    3308  /**
    3309   * Seconds duration setting.
    3310   *
    3311   * @copyright 2012 Petr Skoda (http://skodak.org)
    3312   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3313   */
    3314  class admin_setting_configduration extends admin_setting {
    3315  
    3316      /** @var int default duration unit */
    3317      protected $defaultunit;
    3318  
    3319      /**
    3320       * Constructor
    3321       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
    3322       *                     or 'myplugin/mysetting' for ones in config_plugins.
    3323       * @param string $visiblename localised name
    3324       * @param string $description localised long description
    3325       * @param mixed $defaultsetting string or array depending on implementation
    3326       * @param int $defaultunit - day, week, etc. (in seconds)
    3327       */
    3328      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
    3329          if (is_number($defaultsetting)) {
    3330              $defaultsetting = self::parse_seconds($defaultsetting);
    3331          }
    3332          $units = self::get_units();
    3333          if (isset($units[$defaultunit])) {
    3334              $this->defaultunit = $defaultunit;
    3335          } else {
    3336              $this->defaultunit = 86400;
    3337          }
    3338          parent::__construct($name, $visiblename, $description, $defaultsetting);
    3339      }
    3340  
    3341      /**
    3342       * Returns selectable units.
    3343       * @static
    3344       * @return array
    3345       */
    3346      protected static function get_units() {
    3347          return array(
    3348              604800 => get_string('weeks'),
    3349              86400 => get_string('days'),
    3350              3600 => get_string('hours'),
    3351              60 => get_string('minutes'),
    3352              1 => get_string('seconds'),
    3353          );
    3354      }
    3355  
    3356      /**
    3357       * Converts seconds to some more user friendly string.
    3358       * @static
    3359       * @param int $seconds
    3360       * @return string
    3361       */
    3362      protected static function get_duration_text($seconds) {
    3363          if (empty($seconds)) {
    3364              return get_string('none');
    3365          }
    3366          $data = self::parse_seconds($seconds);
    3367          switch ($data['u']) {
    3368              case (60*60*24*7):
    3369                  return get_string('numweeks', '', $data['v']);
    3370              case (60*60*24):
    3371                  return get_string('numdays', '', $data['v']);
    3372              case (60*60):
    3373                  return get_string('numhours', '', $data['v']);
    3374              case (60):
    3375                  return get_string('numminutes', '', $data['v']);
    3376              default:
    3377                  return get_string('numseconds', '', $data['v']*$data['u']);
    3378          }
    3379      }
    3380  
    3381      /**
    3382       * Finds suitable units for given duration.
    3383       * @static
    3384       * @param int $seconds
    3385       * @return array
    3386       */
    3387      protected static function parse_seconds($seconds) {
    3388          foreach (self::get_units() as $unit => $unused) {
    3389              if ($seconds % $unit === 0) {
    3390                  return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
    3391              }
    3392          }
    3393          return array('v'=>(int)$seconds, 'u'=>1);
    3394      }
    3395  
    3396      /**
    3397       * Get the selected duration as array.
    3398       *
    3399       * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
    3400       */
    3401      public function get_setting() {
    3402          $seconds = $this->config_read($this->name);
    3403          if (is_null($seconds)) {
    3404              return null;
    3405          }
    3406  
    3407          return self::parse_seconds($seconds);
    3408      }
    3409  
    3410      /**
    3411       * Store the duration as seconds.
    3412       *
    3413       * @param array $data Must be form 'h'=>xx, 'm'=>xx
    3414       * @return bool true if success, false if not
    3415       */
    3416      public function write_setting($data) {
    3417          if (!is_array($data)) {
    3418              return '';
    3419          }
    3420  
    3421          $seconds = (int)($data['v']*$data['u']);
    3422          if ($seconds < 0) {
    3423              return get_string('errorsetting', 'admin');
    3424          }
    3425  
    3426          $result = $this->config_write($this->name, $seconds);
    3427          return ($result ? '' : get_string('errorsetting', 'admin'));
    3428      }
    3429  
    3430      /**
    3431       * Returns duration text+select fields.
    3432       *
    3433       * @param array $data Must be form 'v'=>xx, 'u'=>xx
    3434       * @param string $query
    3435       * @return string duration text+select fields and wrapping div(s)
    3436       */
    3437      public function output_html($data, $query='') {
    3438          $default = $this->get_defaultsetting();
    3439  
    3440          if (is_number($default)) {
    3441              $defaultinfo = self::get_duration_text($default);
    3442          } else if (is_array($default)) {
    3443              $defaultinfo = self::get_duration_text($default['v']*$default['u']);
    3444          } else {
    3445              $defaultinfo = null;
    3446          }
    3447  
    3448          $units = self::get_units();
    3449  
    3450          $return = '<div class="form-duration defaultsnext">';
    3451          $return .= '<input type="text" size="5" id="'.$this->get_id().'v" name="'.$this->get_full_name().'[v]" value="'.s($data['v']).'" />';
    3452          $return .= '<select id="'.$this->get_id().'u" name="'.$this->get_full_name().'[u]">';
    3453          foreach ($units as $val => $text) {
    3454              $selected = '';
    3455              if ($data['v'] == 0) {
    3456                  if ($val == $this->defaultunit) {
    3457                      $selected = ' selected="selected"';
    3458                  }
    3459              } else if ($val == $data['u']) {
    3460                  $selected = ' selected="selected"';
    3461              }
    3462              $return .= '<option value="'.$val.'"'.$selected.'>'.$text.'</option>';
    3463          }
    3464          $return .= '</select></div>';
    3465          return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
    3466      }
    3467  }
    3468  
    3469  
    3470  /**
    3471   * Used to validate a textarea used for ip addresses
    3472   *
    3473   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3474   */
    3475  class admin_setting_configiplist extends admin_setting_configtextarea {
    3476  
    3477      /**
    3478       * Validate the contents of the textarea as IP addresses
    3479       *
    3480       * Used to validate a new line separated list of IP addresses collected from
    3481       * a textarea control
    3482       *
    3483       * @param string $data A list of IP Addresses separated by new lines
    3484       * @return mixed bool true for success or string:error on failure
    3485       */
    3486      public function validate($data) {
    3487          if(!empty($data)) {
    3488              $ips = explode("\n", $data);
    3489          } else {
    3490              return true;
    3491          }
    3492          $result = true;
    3493          foreach($ips as $ip) {
    3494              $ip = trim($ip);
    3495              if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
    3496                  preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
    3497                  preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
    3498                  $result = true;
    3499              } else {
    3500                  $result = false;
    3501                  break;
    3502              }
    3503          }
    3504          if($result) {
    3505              return true;
    3506          } else {
    3507              return get_string('validateerror', 'admin');
    3508          }
    3509      }
    3510  }
    3511  
    3512  
    3513  /**
    3514   * An admin setting for selecting one or more users who have a capability
    3515   * in the system context
    3516   *
    3517   * An admin setting for selecting one or more users, who have a particular capability
    3518   * in the system context. Warning, make sure the list will never be too long. There is
    3519   * no paging or searching of this list.
    3520   *
    3521   * To correctly get a list of users from this config setting, you need to call the
    3522   * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
    3523   *
    3524   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3525   */
    3526  class admin_setting_users_with_capability extends admin_setting_configmultiselect {
    3527      /** @var string The capabilities name */
    3528      protected $capability;
    3529      /** @var int include admin users too */
    3530      protected $includeadmins;
    3531  
    3532      /**
    3533       * Constructor.
    3534       *
    3535       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
    3536       * @param string $visiblename localised name
    3537       * @param string $description localised long description
    3538       * @param array $defaultsetting array of usernames
    3539       * @param string $capability string capability name.
    3540       * @param bool $includeadmins include administrators
    3541       */
    3542      function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
    3543          $this->capability    = $capability;
    3544          $this->includeadmins = $includeadmins;
    3545          parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
    3546      }
    3547  
    3548      /**
    3549       * Load all of the uses who have the capability into choice array
    3550       *
    3551       * @return bool Always returns true
    3552       */
    3553      function load_choices() {
    3554          if (is_array($this->choices)) {
    3555              return true;
    3556          }
    3557          list($sort, $sortparams) = users_order_by_sql('u');
    3558          if (!empty($sortparams)) {
    3559              throw new coding_exception('users_order_by_sql returned some query parameters. ' .
    3560                      'This is unexpected, and a problem because there is no way to pass these ' .
    3561                      'parameters to get_users_by_capability. See MDL-34657.');
    3562          }
    3563          $userfields = 'u.id, u.username, ' . get_all_user_name_fields(true, 'u');
    3564          $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort);
    3565          $this->choices = array(
    3566              '$@NONE@$' => get_string('nobody'),
    3567              '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
    3568          );
    3569          if ($this->includeadmins) {
    3570              $admins = get_admins();
    3571              foreach ($admins as $user) {
    3572                  $this->choices[$user->id] = fullname($user);
    3573              }
    3574          }
    3575          if (is_array($users)) {
    3576              foreach ($users as $user) {
    3577                  $this->choices[$user->id] = fullname($user);
    3578              }
    3579          }
    3580          return true;
    3581      }
    3582  
    3583      /**
    3584       * Returns the default setting for class
    3585       *
    3586       * @return mixed Array, or string. Empty string if no default
    3587       */
    3588      public function get_defaultsetting() {
    3589          $this->load_choices();
    3590          $defaultsetting = parent::get_defaultsetting();
    3591          if (empty($defaultsetting)) {
    3592              return array('$@NONE@$');
    3593          } else if (array_key_exists($defaultsetting, $this->choices)) {
    3594                  return $defaultsetting;
    3595              } else {
    3596                  return '';
    3597              }
    3598      }
    3599  
    3600      /**
    3601       * Returns the current setting
    3602       *
    3603       * @return mixed array or string
    3604       */
    3605      public function get_setting() {
    3606          $result = parent::get_setting();
    3607          if ($result === null) {
    3608              // this is necessary for settings upgrade
    3609              return null;
    3610          }
    3611          if (empty($result)) {
    3612              $result = array('$@NONE@$');
    3613          }
    3614          return $result;
    3615      }
    3616  
    3617      /**
    3618       * Save the chosen setting provided as $data
    3619       *
    3620       * @param array $data
    3621       * @return mixed string or array
    3622       */
    3623      public function write_setting($data) {
    3624      // If all is selected, remove any explicit options.
    3625          if (in_array('$@ALL@$', $data)) {
    3626              $data = array('$@ALL@$');
    3627          }
    3628          // None never needs to be written to the DB.
    3629          if (in_array('$@NONE@$', $data)) {
    3630              unset($data[array_search('$@NONE@$', $data)]);
    3631          }
    3632          return parent::write_setting($data);
    3633      }
    3634  }
    3635  
    3636  
    3637  /**
    3638   * Special checkbox for calendar - resets SESSION vars.
    3639   *
    3640   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3641   */
    3642  class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
    3643      /**
    3644       * Calls the parent::__construct with default values
    3645       *
    3646       * name =>  calendar_adminseesall
    3647       * visiblename => get_string('adminseesall', 'admin')
    3648       * description => get_string('helpadminseesall', 'admin')
    3649       * defaultsetting => 0
    3650       */
    3651      public function __construct() {
    3652          parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
    3653              get_string('helpadminseesall', 'admin'), '0');
    3654      }
    3655  
    3656      /**
    3657       * Stores the setting passed in $data
    3658       *
    3659       * @param mixed gets converted to string for comparison
    3660       * @return string empty string or error message
    3661       */
    3662      public function write_setting($data) {
    3663          global $SESSION;
    3664          return parent::write_setting($data);
    3665      }
    3666  }
    3667  
    3668  /**
    3669   * Special select for settings that are altered in setup.php and can not be altered on the fly
    3670   *
    3671   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3672   */
    3673  class admin_setting_special_selectsetup extends admin_setting_configselect {
    3674      /**
    3675       * Reads the setting directly from the database
    3676       *
    3677       * @return mixed
    3678       */
    3679      public function get_setting() {
    3680      // read directly from db!
    3681          return get_config(NULL, $this->name);
    3682      }
    3683  
    3684      /**
    3685       * Save the setting passed in $data
    3686       *
    3687       * @param string $data The setting to save
    3688       * @return string empty or error message
    3689       */
    3690      public function write_setting($data) {
    3691          global $CFG;
    3692          // do not change active CFG setting!
    3693          $current = $CFG->{$this->name};
    3694          $result = parent::write_setting($data);
    3695          $CFG->{$this->name} = $current;
    3696          return $result;
    3697      }
    3698  }
    3699  
    3700  
    3701  /**
    3702   * Special select for frontpage - stores data in course table
    3703   *
    3704   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3705   */
    3706  class admin_setting_sitesetselect extends admin_setting_configselect {
    3707      /**
    3708       * Returns the site name for the selected site
    3709       *
    3710       * @see get_site()
    3711       * @return string The site name of the selected site
    3712       */
    3713      public function get_setting() {
    3714          $site = course_get_format(get_site())->get_course();
    3715          return $site->{$this->name};
    3716      }
    3717  
    3718      /**
    3719       * Updates the database and save the setting
    3720       *
    3721       * @param string data
    3722       * @return string empty or error message
    3723       */
    3724      public function write_setting($data) {
    3725          global $DB, $SITE, $COURSE;
    3726          if (!in_array($data, array_keys($this->choices))) {
    3727              return get_string('errorsetting', 'admin');
    3728          }
    3729          $record = new stdClass();
    3730          $record->id           = SITEID;
    3731          $temp                 = $this->name;
    3732          $record->$temp        = $data;
    3733          $record->timemodified = time();
    3734  
    3735          course_get_format($SITE)->update_course_format_options($record);
    3736          $DB->update_record('course', $record);
    3737  
    3738          // Reset caches.
    3739          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
    3740          if ($SITE->id == $COURSE->id) {
    3741              $COURSE = $SITE;
    3742          }
    3743          format_base::reset_course_cache($SITE->id);
    3744  
    3745          return '';
    3746  
    3747      }
    3748  }
    3749  
    3750  
    3751  /**
    3752   * Select for blog's bloglevel setting: if set to 0, will set blog_menu
    3753   * block to hidden.
    3754   *
    3755   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3756   */
    3757  class admin_setting_bloglevel extends admin_setting_configselect {
    3758      /**
    3759       * Updates the database and save the setting
    3760       *
    3761       * @param string data
    3762       * @return string empty or error message
    3763       */
    3764      public function write_setting($data) {
    3765          global $DB, $CFG;
    3766          if ($data == 0) {
    3767              $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
    3768              foreach ($blogblocks as $block) {
    3769                  $DB->set_field('block', 'visible', 0, array('id' => $block->id));
    3770              }
    3771          } else {
    3772              // reenable all blocks only when switching from disabled blogs
    3773              if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
    3774                  $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
    3775                  foreach ($blogblocks as $block) {
    3776                      $DB->set_field('block', 'visible', 1, array('id' => $block->id));
    3777                  }
    3778              }
    3779          }
    3780          return parent::write_setting($data);
    3781      }
    3782  }
    3783  
    3784  
    3785  /**
    3786   * Special select - lists on the frontpage - hacky
    3787   *
    3788   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3789   */
    3790  class admin_setting_courselist_frontpage extends admin_setting {
    3791      /** @var array Array of choices value=>label */
    3792      public $choices;
    3793  
    3794      /**
    3795       * Construct override, requires one param
    3796       *
    3797       * @param bool $loggedin Is the user logged in
    3798       */
    3799      public function __construct($loggedin) {
    3800          global $CFG;
    3801          require_once($CFG->dirroot.'/course/lib.php');
    3802          $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
    3803          $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
    3804          $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
    3805          $defaults    = array(FRONTPAGEALLCOURSELIST);
    3806          parent::__construct($name, $visiblename, $description, $defaults);
    3807      }
    3808  
    3809      /**
    3810       * Loads the choices available
    3811       *
    3812       * @return bool always returns true
    3813       */
    3814      public function load_choices() {
    3815          if (is_array($this->choices)) {
    3816              return true;
    3817          }
    3818          $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
    3819              FRONTPAGEALLCOURSELIST => get_string('frontpagecourselist'),
    3820              FRONTPAGEENROLLEDCOURSELIST => get_string('frontpageenrolledcourselist'),
    3821              FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
    3822              FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
    3823              FRONTPAGECOURSESEARCH  => get_string('frontpagecoursesearch'),
    3824              'none'                 => get_string('none'));
    3825          if ($this->name === 'frontpage') {
    3826              unset($this->choices[FRONTPAGEENROLLEDCOURSELIST]);
    3827          }
    3828          return true;
    3829      }
    3830  
    3831      /**
    3832       * Returns the selected settings
    3833       *
    3834       * @param mixed array or setting or null
    3835       */
    3836      public function get_setting() {
    3837          $result = $this->config_read($this->name);
    3838          if (is_null($result)) {
    3839              return NULL;
    3840          }
    3841          if ($result === '') {
    3842              return array();
    3843          }
    3844          return explode(',', $result);
    3845      }
    3846  
    3847      /**
    3848       * Save the selected options
    3849       *
    3850       * @param array $data
    3851       * @return mixed empty string (data is not an array) or bool true=success false=failure
    3852       */
    3853      public function write_setting($data) {
    3854          if (!is_array($data)) {
    3855              return '';
    3856          }
    3857          $this->load_choices();
    3858          $save = array();
    3859          foreach($data as $datum) {
    3860              if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
    3861                  continue;
    3862              }
    3863              $save[$datum] = $datum; // no duplicates
    3864          }
    3865          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
    3866      }
    3867  
    3868      /**
    3869       * Return XHTML select field and wrapping div
    3870       *
    3871       * @todo Add vartype handling to make sure $data is an array
    3872       * @param array $data Array of elements to select by default
    3873       * @return string XHTML select field and wrapping div
    3874       */
    3875      public function output_html($data, $query='') {
    3876          $this->load_choices();
    3877          $currentsetting = array();
    3878          foreach ($data as $key) {
    3879              if ($key != 'none' and array_key_exists($key, $this->choices)) {
    3880                  $currentsetting[] = $key; // already selected first
    3881              }
    3882          }
    3883  
    3884          $return = '<div class="form-group">';
    3885          for ($i = 0; $i < count($this->choices) - 1; $i++) {
    3886              if (!array_key_exists($i, $currentsetting)) {
    3887                  $currentsetting[$i] = 'none'; //none
    3888              }
    3889              $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
    3890              foreach ($this->choices as $key => $value) {
    3891                  $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
    3892              }
    3893              $return .= '</select>';
    3894              if ($i !== count($this->choices) - 2) {
    3895                  $return .= '<br />';
    3896              }
    3897          }
    3898          $return .= '</div>';
    3899  
    3900          return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
    3901      }
    3902  }
    3903  
    3904  
    3905  /**
    3906   * Special checkbox for frontpage - stores data in course table
    3907   *
    3908   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3909   */
    3910  class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
    3911      /**
    3912       * Returns the current sites name
    3913       *
    3914       * @return string
    3915       */
    3916      public function get_setting() {
    3917          $site = course_get_format(get_site())->get_course();
    3918          return $site->{$this->name};
    3919      }
    3920  
    3921      /**
    3922       * Save the selected setting
    3923       *
    3924       * @param string $data The selected site
    3925       * @return string empty string or error message
    3926       */
    3927      public function write_setting($data) {
    3928          global $DB, $SITE, $COURSE;
    3929          $record = new stdClass();
    3930          $record->id            = $SITE->id;
    3931          $record->{$this->name} = ($data == '1' ? 1 : 0);
    3932          $record->timemodified  = time();
    3933  
    3934          course_get_format($SITE)->update_course_format_options($record);
    3935          $DB->update_record('course', $record);
    3936  
    3937          // Reset caches.
    3938          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
    3939          if ($SITE->id == $COURSE->id) {
    3940              $COURSE = $SITE;
    3941          }
    3942          format_base::reset_course_cache($SITE->id);
    3943  
    3944          return '';
    3945      }
    3946  }
    3947  
    3948  /**
    3949   * Special text for frontpage - stores data in course table.
    3950   * Empty string means not set here. Manual setting is required.
    3951   *
    3952   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3953   */
    3954  class admin_setting_sitesettext extends admin_setting_configtext {
    3955      /**
    3956       * Return the current setting
    3957       *
    3958       * @return mixed string or null
    3959       */
    3960      public function get_setting() {
    3961          $site = course_get_format(get_site())->get_course();
    3962          return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
    3963      }
    3964  
    3965      /**
    3966       * Validate the selected data
    3967       *
    3968       * @param string $data The selected value to validate
    3969       * @return mixed true or message string
    3970       */
    3971      public function validate($data) {
    3972          global $DB, $SITE;
    3973          $cleaned = clean_param($data, PARAM_TEXT);
    3974          if ($cleaned === '') {
    3975              return get_string('required');
    3976          }
    3977          if ($this->name ==='shortname' &&
    3978                  $DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data, $SITE->id))) {
    3979              return get_string('shortnametaken', 'error', $data);
    3980          }
    3981          if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
    3982              return true;
    3983          } else {
    3984              return get_string('validateerror', 'admin');
    3985          }
    3986      }
    3987  
    3988      /**
    3989       * Save the selected setting
    3990       *
    3991       * @param string $data The selected value
    3992       * @return string empty or error message
    3993       */
    3994      public function write_setting($data) {
    3995          global $DB, $SITE, $COURSE;
    3996          $data = trim($data);
    3997          $validated = $this->validate($data);
    3998          if ($validated !== true) {
    3999              return $validated;
    4000          }
    4001  
    4002          $record = new stdClass();
    4003          $record->id            = $SITE->id;
    4004          $record->{$this->name} = $data;
    4005          $record->timemodified  = time();
    4006  
    4007          course_get_format($SITE)->update_course_format_options($record);
    4008          $DB->update_record('course', $record);
    4009  
    4010          // Reset caches.
    4011          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
    4012          if ($SITE->id == $COURSE->id) {
    4013              $COURSE = $SITE;
    4014          }
    4015          format_base::reset_course_cache($SITE->id);
    4016  
    4017          return '';
    4018      }
    4019  }
    4020  
    4021  
    4022  /**
    4023   * Special text editor for site description.
    4024   *
    4025   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4026   */
    4027  class admin_setting_special_frontpagedesc extends admin_setting {
    4028      /**
    4029       * Calls parent::__construct with specific arguments
    4030       */
    4031      public function __construct() {
    4032          parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
    4033          editors_head_setup();
    4034      }
    4035  
    4036      /**
    4037       * Return the current setting
    4038       * @return string The current setting
    4039       */
    4040      public function get_setting() {
    4041          $site = course_get_format(get_site())->get_course();
    4042          return $site->{$this->name};
    4043      }
    4044  
    4045      /**
    4046       * Save the new setting
    4047       *
    4048       * @param string $data The new value to save
    4049       * @return string empty or error message
    4050       */
    4051      public function write_setting($data) {
    4052          global $DB, $SITE, $COURSE;
    4053          $record = new stdClass();
    4054          $record->id            = $SITE->id;
    4055          $record->{$this->name} = $data;
    4056          $record->timemodified  = time();
    4057  
    4058          course_get_format($SITE)->update_course_format_options($record);
    4059          $DB->update_record('course', $record);
    4060  
    4061          // Reset caches.
    4062          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
    4063          if ($SITE->id == $COURSE->id) {
    4064              $COURSE = $SITE;
    4065          }
    4066          format_base::reset_course_cache($SITE->id);
    4067  
    4068          return '';
    4069      }
    4070  
    4071      /**
    4072       * Returns XHTML for the field plus wrapping div
    4073       *
    4074       * @param string $data The current value
    4075       * @param string $query
    4076       * @return string The XHTML output
    4077       */
    4078      public function output_html($data, $query='') {
    4079          global $CFG;
    4080  
    4081          $return = '<div class="form-htmlarea">'.print_textarea(true, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
    4082  
    4083          return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
    4084      }
    4085  }
    4086  
    4087  
    4088  /**
    4089   * Administration interface for emoticon_manager settings.
    4090   *
    4091   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4092   */
    4093  class admin_setting_emoticons extends admin_setting {
    4094  
    4095      /**
    4096       * Calls parent::__construct with specific args
    4097       */
    4098      public function __construct() {
    4099          global $CFG;
    4100  
    4101          $manager = get_emoticon_manager();
    4102          $defaults = $this->prepare_form_data($manager->default_emoticons());
    4103          parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
    4104      }
    4105  
    4106      /**
    4107       * Return the current setting(s)
    4108       *
    4109       * @return array Current settings array
    4110       */
    4111      public function get_setting() {
    4112          global $CFG;
    4113  
    4114          $manager = get_emoticon_manager();
    4115  
    4116          $config = $this->config_read($this->name);
    4117          if (is_null($config)) {
    4118              return null;
    4119          }
    4120  
    4121          $config = $manager->decode_stored_config($config);
    4122          if (is_null($config)) {
    4123              return null;
    4124          }
    4125  
    4126          return $this->prepare_form_data($config);
    4127      }
    4128  
    4129      /**
    4130       * Save selected settings
    4131       *
    4132       * @param array $data Array of settings to save
    4133       * @return bool
    4134       */
    4135      public function write_setting($data) {
    4136  
    4137          $manager = get_emoticon_manager();
    4138          $emoticons = $this->process_form_data($data);
    4139  
    4140          if ($emoticons === false) {
    4141              return false;
    4142          }
    4143  
    4144          if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
    4145              return ''; // success
    4146          } else {
    4147              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
    4148          }
    4149      }
    4150  
    4151      /**
    4152       * Return XHTML field(s) for options
    4153       *
    4154       * @param array $data Array of options to set in HTML
    4155       * @return string XHTML string for the fields and wrapping div(s)
    4156       */
    4157      public function output_html($data, $query='') {
    4158          global $OUTPUT;
    4159  
    4160          $out  = html_writer::start_tag('table', array('id' => 'emoticonsetting', 'class' => 'admintable generaltable'));
    4161          $out .= html_writer::start_tag('thead');
    4162          $out .= html_writer::start_tag('tr');
    4163          $out .= html_writer::tag('th', get_string('emoticontext', 'admin'));
    4164          $out .= html_writer::tag('th', get_string('emoticonimagename', 'admin'));
    4165          $out .= html_writer::tag('th', get_string('emoticoncomponent', 'admin'));
    4166          $out .= html_writer::tag('th', get_string('emoticonalt', 'admin'), array('colspan' => 2));
    4167          $out .= html_writer::tag('th', '');
    4168          $out .= html_writer::end_tag('tr');
    4169          $out .= html_writer::end_tag('thead');
    4170          $out .= html_writer::start_tag('tbody');
    4171          $i = 0;
    4172          foreach($data as $field => $value) {
    4173              switch ($i) {
    4174              case 0:
    4175                  $out .= html_writer::start_tag('tr');
    4176                  $current_text = $value;
    4177                  $current_filename = '';
    4178                  $current_imagecomponent = '';
    4179                  $current_altidentifier = '';
    4180                  $current_altcomponent = '';
    4181              case 1:
    4182                  $current_filename = $value;
    4183              case 2:
    4184                  $current_imagecomponent = $value;
    4185              case 3:
    4186                  $current_altidentifier = $value;
    4187              case 4:
    4188                  $current_altcomponent = $value;
    4189              }
    4190  
    4191              $out .= html_writer::tag('td',
    4192                  html_writer::empty_tag('input',
    4193                      array(
    4194                          'type'  => 'text',
    4195                          'class' => 'form-text',
    4196                          'name'  => $this->get_full_name().'['.$field.']',
    4197                          'value' => $value,
    4198                      )
    4199                  ), array('class' => 'c'.$i)
    4200              );
    4201  
    4202              if ($i == 4) {
    4203                  if (get_string_manager()->string_exists($current_altidentifier, $current_altcomponent)) {
    4204                      $alt = get_string($current_altidentifier, $current_altcomponent);
    4205                  } else {
    4206                      $alt = $current_text;
    4207                  }
    4208                  if ($current_filename) {
    4209                      $out .= html_writer::tag('td', $OUTPUT->render(new pix_emoticon($current_filename, $alt, $current_imagecomponent)));
    4210                  } else {
    4211                      $out .= html_writer::tag('td', '');
    4212                  }
    4213                  $out .= html_writer::end_tag('tr');
    4214                  $i = 0;
    4215              } else {
    4216                  $i++;
    4217              }
    4218  
    4219          }
    4220          $out .= html_writer::end_tag('tbody');
    4221          $out .= html_writer::end_tag('table');
    4222          $out  = html_writer::tag('div', $out, array('class' => 'form-group'));
    4223          $out .= html_writer::tag('div', html_writer::link(new moodle_url('/admin/resetemoticons.php'), get_string('emoticonsreset', 'admin')));
    4224  
    4225          return format_admin_setting($this, $this->visiblename, $out, $this->description, false, '', NULL, $query);
    4226      }
    4227  
    4228      /**
    4229       * Converts the array of emoticon objects provided by {@see emoticon_manager} into admin settings form data
    4230       *
    4231       * @see self::process_form_data()
    4232       * @param array $emoticons array of emoticon objects as returned by {@see emoticon_manager}
    4233       * @return array of form fields and their values
    4234       */
    4235      protected function prepare_form_data(array $emoticons) {
    4236  
    4237          $form = array();
    4238          $i = 0;
    4239          foreach ($emoticons as $emoticon) {
    4240              $form['text'.$i]            = $emoticon->text;
    4241              $form['imagename'.$i]       = $emoticon->imagename;
    4242              $form['imagecomponent'.$i]  = $emoticon->imagecomponent;
    4243              $form['altidentifier'.$i]   = $emoticon->altidentifier;
    4244              $form['altcomponent'.$i]    = $emoticon->altcomponent;
    4245              $i++;
    4246          }
    4247          // add one more blank field set for new object
    4248          $form['text'.$i]            = '';
    4249          $form['imagename'.$i]       = '';
    4250          $form['imagecomponent'.$i]  = '';
    4251          $form['altidentifier'.$i]   = '';
    4252          $form['altcomponent'.$i]    = '';
    4253  
    4254          return $form;
    4255      }
    4256  
    4257      /**
    4258       * Converts the data from admin settings form into an array of emoticon objects
    4259       *
    4260       * @see self::prepare_form_data()
    4261       * @param array $data array of admin form fields and values
    4262       * @return false|array of emoticon objects
    4263       */
    4264      protected function process_form_data(array $form) {
    4265  
    4266          $count = count($form); // number of form field values
    4267  
    4268          if ($count % 5) {
    4269              // we must get five fields per emoticon object
    4270              return false;
    4271          }
    4272  
    4273          $emoticons = array();
    4274          for ($i = 0; $i < $count / 5; $i++) {
    4275              $emoticon                   = new stdClass();
    4276              $emoticon->text             = clean_param(trim($form['text'.$i]), PARAM_NOTAGS);
    4277              $emoticon->imagename        = clean_param(trim($form['imagename'.$i]), PARAM_PATH);
    4278              $emoticon->imagecomponent   = clean_param(trim($form['imagecomponent'.$i]), PARAM_COMPONENT);
    4279              $emoticon->altidentifier    = clean_param(trim($form['altidentifier'.$i]), PARAM_STRINGID);
    4280              $emoticon->altcomponent     = clean_param(trim($form['altcomponent'.$i]), PARAM_COMPONENT);
    4281  
    4282              if (strpos($emoticon->text, ':/') !== false or strpos($emoticon->text, '//') !== false) {
    4283                  // prevent from breaking http://url.addresses by accident
    4284                  $emoticon->text = '';
    4285              }
    4286  
    4287              if (strlen($emoticon->text) < 2) {
    4288                  // do not allow single character emoticons
    4289                  $emoticon->text = '';
    4290              }
    4291  
    4292              if (preg_match('/^[a-zA-Z]+[a-zA-Z0-9]*$/', $emoticon->text)) {
    4293                  // emoticon text must contain some non-alphanumeric character to prevent
    4294                  // breaking HTML tags
    4295                  $emoticon->text = '';
    4296              }
    4297  
    4298              if ($emoticon->text !== '' and $emoticon->imagename !== '' and $emoticon->imagecomponent !== '') {
    4299                  $emoticons[] = $emoticon;
    4300              }
    4301          }
    4302          return $emoticons;
    4303      }
    4304  }
    4305  
    4306  
    4307  /**
    4308   * Special setting for limiting of the list of available languages.
    4309   *
    4310   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4311   */
    4312  class admin_setting_langlist extends admin_setting_configtext {
    4313      /**
    4314       * Calls parent::__construct with specific arguments
    4315       */
    4316      public function __construct() {
    4317          parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
    4318      }
    4319  
    4320      /**
    4321       * Save the new setting
    4322       *
    4323       * @param string $data The new setting
    4324       * @return bool
    4325       */
    4326      public function write_setting($data) {
    4327          $return = parent::write_setting($data);
    4328          get_string_manager()->reset_caches();
    4329          return $return;
    4330      }
    4331  }
    4332  
    4333  
    4334  /**
    4335   * Selection of one of the recognised countries using the list
    4336   * returned by {@link get_list_of_countries()}.
    4337   *
    4338   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4339   */
    4340  class admin_settings_country_select extends admin_setting_configselect {
    4341      protected $includeall;
    4342      public function __construct($name, $visiblename, $description, $defaultsetting, $includeall=false) {
    4343          $this->includeall = $includeall;
    4344          parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
    4345      }
    4346  
    4347      /**
    4348       * Lazy-load the available choices for the select box
    4349       */
    4350      public function load_choices() {
    4351          global $CFG;
    4352          if (is_array($this->choices)) {
    4353              return true;
    4354          }
    4355          $this->choices = array_merge(
    4356                  array('0' => get_string('choosedots')),
    4357                  get_string_manager()->get_list_of_countries($this->includeall));
    4358          return true;
    4359      }
    4360  }
    4361  
    4362  
    4363  /**
    4364   * admin_setting_configselect for the default number of sections in a course,
    4365   * simply so we can lazy-load the choices.
    4366   *
    4367   * @copyright 2011 The Open University
    4368   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4369   */
    4370  class admin_settings_num_course_sections extends admin_setting_configselect {
    4371      public function __construct($name, $visiblename, $description, $defaultsetting) {
    4372          parent::__construct($name, $visiblename, $description, $defaultsetting, array());
    4373      }
    4374  
    4375      /** Lazy-load the available choices for the select box */
    4376      public function load_choices() {
    4377          $max = get_config('moodlecourse', 'maxsections');
    4378          if (!isset($max) || !is_numeric($max)) {
    4379              $max = 52;
    4380          }
    4381          for ($i = 0; $i <= $max; $i++) {
    4382              $this->choices[$i] = "$i";
    4383          }
    4384          return true;
    4385      }
    4386  }
    4387  
    4388  
    4389  /**
    4390   * Course category selection
    4391   *
    4392   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4393   */
    4394  class admin_settings_coursecat_select extends admin_setting_configselect {
    4395      /**
    4396       * Calls parent::__construct with specific arguments
    4397       */
    4398      public function __construct($name, $visiblename, $description, $defaultsetting) {
    4399          parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
    4400      }
    4401  
    4402      /**
    4403       * Load the available choices for the select box
    4404       *
    4405       * @return bool
    4406       */
    4407      public function load_choices() {
    4408          global $CFG;
    4409          require_once($CFG->dirroot.'/course/lib.php');
    4410          if (is_array($this->choices)) {
    4411              return true;
    4412          }
    4413          $this->choices = make_categories_options();
    4414          return true;
    4415      }
    4416  }
    4417  
    4418  
    4419  /**
    4420   * Special control for selecting days to backup
    4421   *
    4422   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4423   */
    4424  class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
    4425      /**
    4426       * Calls parent::__construct with specific arguments
    4427       */
    4428      public function __construct() {
    4429          parent::__construct('backup_auto_weekdays', get_string('automatedbackupschedule','backup'), get_string('automatedbackupschedulehelp','backup'), array(), NULL);
    4430          $this->plugin = 'backup';
    4431      }
    4432  
    4433      /**
    4434       * Load the available choices for the select box
    4435       *
    4436       * @return bool Always returns true
    4437       */
    4438      public function load_choices() {
    4439          if (is_array($this->choices)) {
    4440              return true;
    4441          }
    4442          $this->choices = array();
    4443          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
    4444          foreach ($days as $day) {
    4445              $this->choices[$day] = get_string($day, 'calendar');
    4446          }
    4447          return true;
    4448      }
    4449  }
    4450  
    4451  
    4452  /**
    4453   * Special debug setting
    4454   *
    4455   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4456   */
    4457  class admin_setting_special_debug extends admin_setting_configselect {
    4458      /**
    4459       * Calls parent::__construct with specific arguments
    4460       */
    4461      public function __construct() {
    4462          parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
    4463      }
    4464  
    4465      /**
    4466       * Load the available choices for the select box
    4467       *
    4468       * @return bool
    4469       */
    4470      public function load_choices() {
    4471          if (is_array($this->choices)) {
    4472              return true;
    4473          }
    4474          $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
    4475              DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
    4476              DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
    4477              DEBUG_ALL       => get_string('debugall', 'admin'),
    4478              DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
    4479          return true;
    4480      }
    4481  }
    4482  
    4483  
    4484  /**
    4485   * Special admin control
    4486   *
    4487   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    4488   */
    4489  class admin_setting_special_calendar_weekend extends admin_setting {
    4490      /**
    4491       * Calls parent::__construct with specific arguments
    4492       */
    4493      public function __construct() {
    4494          $name = 'calendar_weekend';
    4495          $visiblename = get_string('calendar_weekend', 'admin');
    4496          $description = get_string('helpweekenddays', 'admin');
    4497          $default = array ('0', '6'); // Saturdays and Sundays
    4498          parent::__construct($name, $visiblename, $description, $default);
    4499      }
    4500  
    4501      /**
    4502       * Gets the current settings as an array
    4503       *
    4504       * @return mixed Null if none, else array of settings
    4505       */
    4506      public function get_setting() {
    4507          $result = $this->config_read($this->name);
    4508          if (is_null($result)) {
    4509              return NULL;
    4510          }
    4511          if ($result === '') {
    4512              return array();
    4513          }
    4514          $settings = array();
    4515          for ($i=0; $i<7; $i++) {