Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
/lib/ -> adminlib.php (source)

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

   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(__DIR__.'/../../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  
 136          $subpluginsfile = "{$base}/db/subplugins.json";
 137          if (file_exists($subpluginsfile)) {
 138              $subplugins = (array) json_decode(file_get_contents($subpluginsfile))->plugintypes;
 139          } else if (file_exists("{$base}/db/subplugins.php")) {
 140              debugging('Use of subplugins.php has been deprecated. ' .
 141                      'Please update your plugin to provide a subplugins.json file instead.',
 142                      DEBUG_DEVELOPER);
 143              $subplugins = [];
 144              include("{$base}/db/subplugins.php");
 145          }
 146  
 147          if (!empty($subplugins)) {
 148              foreach (array_keys($subplugins) as $subplugintype) {
 149                  $instances = core_component::get_plugin_list($subplugintype);
 150                  foreach ($instances as $subpluginname => $notusedpluginpath) {
 151                      uninstall_plugin($subplugintype, $subpluginname);
 152                  }
 153              }
 154          }
 155      }
 156  
 157      $component = $type . '_' . $name;  // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
 158  
 159      if ($type === 'mod') {
 160          $pluginname = $name;  // eg. 'forum'
 161          if (get_string_manager()->string_exists('modulename', $component)) {
 162              $strpluginname = get_string('modulename', $component);
 163          } else {
 164              $strpluginname = $component;
 165          }
 166  
 167      } else {
 168          $pluginname = $component;
 169          if (get_string_manager()->string_exists('pluginname', $component)) {
 170              $strpluginname = get_string('pluginname', $component);
 171          } else {
 172              $strpluginname = $component;
 173          }
 174      }
 175  
 176      echo $OUTPUT->heading($pluginname);
 177  
 178      // Delete all tag areas, collections and instances associated with this plugin.
 179      core_tag_area::uninstall($component);
 180  
 181      // Custom plugin uninstall.
 182      $plugindirectory = core_component::get_plugin_directory($type, $name);
 183      $uninstalllib = $plugindirectory . '/db/uninstall.php';
 184      if (file_exists($uninstalllib)) {
 185          require_once($uninstalllib);
 186          $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall';    // eg. 'xmldb_workshop_uninstall()'
 187          if (function_exists($uninstallfunction)) {
 188              // Do not verify result, let plugin complain if necessary.
 189              $uninstallfunction();
 190          }
 191      }
 192  
 193      // Specific plugin type cleanup.
 194      $plugininfo = core_plugin_manager::instance()->get_plugin_info($component);
 195      if ($plugininfo) {
 196          $plugininfo->uninstall_cleanup();
 197          core_plugin_manager::reset_caches();
 198      }
 199      $plugininfo = null;
 200  
 201      // perform clean-up task common for all the plugin/subplugin types
 202  
 203      //delete the web service functions and pre-built services
 204      require_once($CFG->dirroot.'/lib/externallib.php');
 205      external_delete_descriptions($component);
 206  
 207      // delete calendar events
 208      $DB->delete_records('event', array('modulename' => $pluginname));
 209      $DB->delete_records('event', ['component' => $component]);
 210  
 211      // Delete scheduled tasks.
 212      $DB->delete_records('task_adhoc', ['component' => $component]);
 213      $DB->delete_records('task_scheduled', array('component' => $component));
 214  
 215      // Delete Inbound Message datakeys.
 216      $DB->delete_records_select('messageinbound_datakeys',
 217              'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($component));
 218  
 219      // Delete Inbound Message handlers.
 220      $DB->delete_records('messageinbound_handlers', array('component' => $component));
 221  
 222      // delete all the logs
 223      $DB->delete_records('log', array('module' => $pluginname));
 224  
 225      // delete log_display information
 226      $DB->delete_records('log_display', array('component' => $component));
 227  
 228      // delete the module configuration records
 229      unset_all_config_for_plugin($component);
 230      if ($type === 'mod') {
 231          unset_all_config_for_plugin($pluginname);
 232      }
 233  
 234      // delete message provider
 235      message_provider_uninstall($component);
 236  
 237      // delete the plugin tables
 238      $xmldbfilepath = $plugindirectory . '/db/install.xml';
 239      drop_plugin_tables($component, $xmldbfilepath, false);
 240      if ($type === 'mod' or $type === 'block') {
 241          // non-frankenstyle table prefixes
 242          drop_plugin_tables($name, $xmldbfilepath, false);
 243      }
 244  
 245      // delete the capabilities that were defined by this module
 246      capabilities_cleanup($component);
 247  
 248      // Delete all remaining files in the filepool owned by the component.
 249      $fs = get_file_storage();
 250      $fs->delete_component_files($component);
 251  
 252      // Finally purge all caches.
 253      purge_all_caches();
 254  
 255      // Invalidate the hash used for upgrade detections.
 256      set_config('allversionshash', '');
 257  
 258      echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
 259  }
 260  
 261  /**
 262   * Returns the version of installed component
 263   *
 264   * @param string $component component name
 265   * @param string $source either 'disk' or 'installed' - where to get the version information from
 266   * @return string|bool version number or false if the component is not found
 267   */
 268  function get_component_version($component, $source='installed') {
 269      global $CFG, $DB;
 270  
 271      list($type, $name) = core_component::normalize_component($component);
 272  
 273      // moodle core or a core subsystem
 274      if ($type === 'core') {
 275          if ($source === 'installed') {
 276              if (empty($CFG->version)) {
 277                  return false;
 278              } else {
 279                  return $CFG->version;
 280              }
 281          } else {
 282              if (!is_readable($CFG->dirroot.'/version.php')) {
 283                  return false;
 284              } else {
 285                  $version = null; //initialize variable for IDEs
 286                  include($CFG->dirroot.'/version.php');
 287                  return $version;
 288              }
 289          }
 290      }
 291  
 292      // activity module
 293      if ($type === 'mod') {
 294          if ($source === 'installed') {
 295              if ($CFG->version < 2013092001.02) {
 296                  return $DB->get_field('modules', 'version', array('name'=>$name));
 297              } else {
 298                  return get_config('mod_'.$name, 'version');
 299              }
 300  
 301          } else {
 302              $mods = core_component::get_plugin_list('mod');
 303              if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
 304                  return false;
 305              } else {
 306                  $plugin = new stdClass();
 307                  $plugin->version = null;
 308                  $module = $plugin;
 309                  include($mods[$name].'/version.php');
 310                  return $plugin->version;
 311              }
 312          }
 313      }
 314  
 315      // block
 316      if ($type === 'block') {
 317          if ($source === 'installed') {
 318              if ($CFG->version < 2013092001.02) {
 319                  return $DB->get_field('block', 'version', array('name'=>$name));
 320              } else {
 321                  return get_config('block_'.$name, 'version');
 322              }
 323          } else {
 324              $blocks = core_component::get_plugin_list('block');
 325              if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
 326                  return false;
 327              } else {
 328                  $plugin = new stdclass();
 329                  include($blocks[$name].'/version.php');
 330                  return $plugin->version;
 331              }
 332          }
 333      }
 334  
 335      // all other plugin types
 336      if ($source === 'installed') {
 337          return get_config($type.'_'.$name, 'version');
 338      } else {
 339          $plugins = core_component::get_plugin_list($type);
 340          if (empty($plugins[$name])) {
 341              return false;
 342          } else {
 343              $plugin = new stdclass();
 344              include($plugins[$name].'/version.php');
 345              return $plugin->version;
 346          }
 347      }
 348  }
 349  
 350  /**
 351   * Delete all plugin tables
 352   *
 353   * @param string $name Name of plugin, used as table prefix
 354   * @param string $file Path to install.xml file
 355   * @param bool $feedback defaults to true
 356   * @return bool Always returns true
 357   */
 358  function drop_plugin_tables($name, $file, $feedback=true) {
 359      global $CFG, $DB;
 360  
 361      // first try normal delete
 362      if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
 363          return true;
 364      }
 365  
 366      // then try to find all tables that start with name and are not in any xml file
 367      $used_tables = get_used_table_names();
 368  
 369      $tables = $DB->get_tables();
 370  
 371      /// Iterate over, fixing id fields as necessary
 372      foreach ($tables as $table) {
 373          if (in_array($table, $used_tables)) {
 374              continue;
 375          }
 376  
 377          if (strpos($table, $name) !== 0) {
 378              continue;
 379          }
 380  
 381          // found orphan table --> delete it
 382          if ($DB->get_manager()->table_exists($table)) {
 383              $xmldb_table = new xmldb_table($table);
 384              $DB->get_manager()->drop_table($xmldb_table);
 385          }
 386      }
 387  
 388      return true;
 389  }
 390  
 391  /**
 392   * Returns names of all known tables == tables that moodle knows about.
 393   *
 394   * @return array Array of lowercase table names
 395   */
 396  function get_used_table_names() {
 397      $table_names = array();
 398      $dbdirs = get_db_directories();
 399  
 400      foreach ($dbdirs as $dbdir) {
 401          $file = $dbdir.'/install.xml';
 402  
 403          $xmldb_file = new xmldb_file($file);
 404  
 405          if (!$xmldb_file->fileExists()) {
 406              continue;
 407          }
 408  
 409          $loaded    = $xmldb_file->loadXMLStructure();
 410          $structure = $xmldb_file->getStructure();
 411  
 412          if ($loaded and $tables = $structure->getTables()) {
 413              foreach($tables as $table) {
 414                  $table_names[] = strtolower($table->getName());
 415              }
 416          }
 417      }
 418  
 419      return $table_names;
 420  }
 421  
 422  /**
 423   * Returns list of all directories where we expect install.xml files
 424   * @return array Array of paths
 425   */
 426  function get_db_directories() {
 427      global $CFG;
 428  
 429      $dbdirs = array();
 430  
 431      /// First, the main one (lib/db)
 432      $dbdirs[] = $CFG->libdir.'/db';
 433  
 434      /// Then, all the ones defined by core_component::get_plugin_types()
 435      $plugintypes = core_component::get_plugin_types();
 436      foreach ($plugintypes as $plugintype => $pluginbasedir) {
 437          if ($plugins = core_component::get_plugin_list($plugintype)) {
 438              foreach ($plugins as $plugin => $plugindir) {
 439                  $dbdirs[] = $plugindir.'/db';
 440              }
 441          }
 442      }
 443  
 444      return $dbdirs;
 445  }
 446  
 447  /**
 448   * Try to obtain or release the cron lock.
 449   * @param string  $name  name of lock
 450   * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionally
 451   * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
 452   * @return bool true if lock obtained
 453   */
 454  function set_cron_lock($name, $until, $ignorecurrent=false) {
 455      global $DB;
 456      if (empty($name)) {
 457          debugging("Tried to get a cron lock for a null fieldname");
 458          return false;
 459      }
 460  
 461      // remove lock by force == remove from config table
 462      if (is_null($until)) {
 463          set_config($name, null);
 464          return true;
 465      }
 466  
 467      if (!$ignorecurrent) {
 468          // read value from db - other processes might have changed it
 469          $value = $DB->get_field('config', 'value', array('name'=>$name));
 470  
 471          if ($value and $value > time()) {
 472              //lock active
 473              return false;
 474          }
 475      }
 476  
 477      set_config($name, $until);
 478      return true;
 479  }
 480  
 481  /**
 482   * Test if and critical warnings are present
 483   * @return bool
 484   */
 485  function admin_critical_warnings_present() {
 486      global $SESSION;
 487  
 488      if (!has_capability('moodle/site:config', context_system::instance())) {
 489          return 0;
 490      }
 491  
 492      if (!isset($SESSION->admin_critical_warning)) {
 493          $SESSION->admin_critical_warning = 0;
 494          if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
 495              $SESSION->admin_critical_warning = 1;
 496          }
 497      }
 498  
 499      return $SESSION->admin_critical_warning;
 500  }
 501  
 502  /**
 503   * Detects if float supports at least 10 decimal digits
 504   *
 505   * Detects if float supports at least 10 decimal digits
 506   * and also if float-->string conversion works as expected.
 507   *
 508   * @return bool true if problem found
 509   */
 510  function is_float_problem() {
 511      $num1 = 2009010200.01;
 512      $num2 = 2009010200.02;
 513  
 514      return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
 515  }
 516  
 517  /**
 518   * Try to verify that dataroot is not accessible from web.
 519   *
 520   * Try to verify that dataroot is not accessible from web.
 521   * It is not 100% correct but might help to reduce number of vulnerable sites.
 522   * Protection from httpd.conf and .htaccess is not detected properly.
 523   *
 524   * @uses INSECURE_DATAROOT_WARNING
 525   * @uses INSECURE_DATAROOT_ERROR
 526   * @param bool $fetchtest try to test public access by fetching file, default false
 527   * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
 528   */
 529  function is_dataroot_insecure($fetchtest=false) {
 530      global $CFG;
 531  
 532      $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
 533  
 534      $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
 535      $rp = strrev(trim($rp, '/'));
 536      $rp = explode('/', $rp);
 537      foreach($rp as $r) {
 538          if (strpos($siteroot, '/'.$r.'/') === 0) {
 539              $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
 540          } else {
 541              break; // probably alias root
 542          }
 543      }
 544  
 545      $siteroot = strrev($siteroot);
 546      $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
 547  
 548      if (strpos($dataroot, $siteroot) !== 0) {
 549          return false;
 550      }
 551  
 552      if (!$fetchtest) {
 553          return INSECURE_DATAROOT_WARNING;
 554      }
 555  
 556      // now try all methods to fetch a test file using http protocol
 557  
 558      $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
 559      preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
 560      $httpdocroot = $matches[1];
 561      $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
 562      make_upload_directory('diag');
 563      $testfile = $CFG->dataroot.'/diag/public.txt';
 564      if (!file_exists($testfile)) {
 565          file_put_contents($testfile, 'test file, do not delete');
 566          @chmod($testfile, $CFG->filepermissions);
 567      }
 568      $teststr = trim(file_get_contents($testfile));
 569      if (empty($teststr)) {
 570      // hmm, strange
 571          return INSECURE_DATAROOT_WARNING;
 572      }
 573  
 574      $testurl = $datarooturl.'/diag/public.txt';
 575      if (extension_loaded('curl') and
 576          !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
 577          !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
 578          ($ch = @curl_init($testurl)) !== false) {
 579          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 580          curl_setopt($ch, CURLOPT_HEADER, false);
 581          $data = curl_exec($ch);
 582          if (!curl_errno($ch)) {
 583              $data = trim($data);
 584              if ($data === $teststr) {
 585                  curl_close($ch);
 586                  return INSECURE_DATAROOT_ERROR;
 587              }
 588          }
 589          curl_close($ch);
 590      }
 591  
 592      if ($data = @file_get_contents($testurl)) {
 593          $data = trim($data);
 594          if ($data === $teststr) {
 595              return INSECURE_DATAROOT_ERROR;
 596          }
 597      }
 598  
 599      preg_match('|https?://([^/]+)|i', $testurl, $matches);
 600      $sitename = $matches[1];
 601      $error = 0;
 602      if ($fp = @fsockopen($sitename, 80, $error)) {
 603          preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
 604          $localurl = $matches[1];
 605          $out = "GET $localurl HTTP/1.1\r\n";
 606          $out .= "Host: $sitename\r\n";
 607          $out .= "Connection: Close\r\n\r\n";
 608          fwrite($fp, $out);
 609          $data = '';
 610          $incoming = false;
 611          while (!feof($fp)) {
 612              if ($incoming) {
 613                  $data .= fgets($fp, 1024);
 614              } else if (@fgets($fp, 1024) === "\r\n") {
 615                      $incoming = true;
 616                  }
 617          }
 618          fclose($fp);
 619          $data = trim($data);
 620          if ($data === $teststr) {
 621              return INSECURE_DATAROOT_ERROR;
 622          }
 623      }
 624  
 625      return INSECURE_DATAROOT_WARNING;
 626  }
 627  
 628  /**
 629   * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
 630   */
 631  function enable_cli_maintenance_mode() {
 632      global $CFG, $SITE;
 633  
 634      if (file_exists("$CFG->dataroot/climaintenance.html")) {
 635          unlink("$CFG->dataroot/climaintenance.html");
 636      }
 637  
 638      if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
 639          $data = $CFG->maintenance_message;
 640          $data = bootstrap_renderer::early_error_content($data, null, null, null);
 641          $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
 642  
 643      } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
 644          $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
 645  
 646      } else {
 647          $data = get_string('sitemaintenance', 'admin');
 648          $data = bootstrap_renderer::early_error_content($data, null, null, null);
 649          $data = bootstrap_renderer::plain_page(get_string('sitemaintenancetitle', 'admin',
 650              format_string($SITE->fullname, true, ['context' => context_system::instance()])), $data);
 651      }
 652  
 653      file_put_contents("$CFG->dataroot/climaintenance.html", $data);
 654      chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
 655  }
 656  
 657  /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
 658  
 659  
 660  /**
 661   * Interface for anything appearing in the admin tree
 662   *
 663   * The interface that is implemented by anything that appears in the admin tree
 664   * block. It forces inheriting classes to define a method for checking user permissions
 665   * and methods for finding something in the admin tree.
 666   *
 667   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 668   */
 669  interface part_of_admin_tree {
 670  
 671  /**
 672   * Finds a named part_of_admin_tree.
 673   *
 674   * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
 675   * and not parentable_part_of_admin_tree, then this function should only check if
 676   * $this->name matches $name. If it does, it should return a reference to $this,
 677   * otherwise, it should return a reference to NULL.
 678   *
 679   * If a class inherits parentable_part_of_admin_tree, this method should be called
 680   * recursively on all child objects (assuming, of course, the parent object's name
 681   * doesn't match the search criterion).
 682   *
 683   * @param string $name The internal name of the part_of_admin_tree we're searching for.
 684   * @return mixed An object reference or a NULL reference.
 685   */
 686      public function locate($name);
 687  
 688      /**
 689       * Removes named part_of_admin_tree.
 690       *
 691       * @param string $name The internal name of the part_of_admin_tree we want to remove.
 692       * @return bool success.
 693       */
 694      public function prune($name);
 695  
 696      /**
 697       * Search using query
 698       * @param string $query
 699       * @return mixed array-object structure of found settings and pages
 700       */
 701      public function search($query);
 702  
 703      /**
 704       * Verifies current user's access to this part_of_admin_tree.
 705       *
 706       * Used to check if the current user has access to this part of the admin tree or
 707       * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
 708       * then this method is usually just a call to has_capability() in the site context.
 709       *
 710       * If a class inherits parentable_part_of_admin_tree, this method should return the
 711       * logical OR of the return of check_access() on all child objects.
 712       *
 713       * @return bool True if the user has access, false if she doesn't.
 714       */
 715      public function check_access();
 716  
 717      /**
 718       * Mostly useful for removing of some parts of the tree in admin tree block.
 719       *
 720       * @return True is hidden from normal list view
 721       */
 722      public function is_hidden();
 723  
 724      /**
 725       * Show we display Save button at the page bottom?
 726       * @return bool
 727       */
 728      public function show_save();
 729  }
 730  
 731  
 732  /**
 733   * Interface implemented by any part_of_admin_tree that has children.
 734   *
 735   * The interface implemented by any part_of_admin_tree that can be a parent
 736   * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
 737   * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
 738   * include an add method for adding other part_of_admin_tree objects as children.
 739   *
 740   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 741   */
 742  interface parentable_part_of_admin_tree extends part_of_admin_tree {
 743  
 744  /**
 745   * Adds a part_of_admin_tree object to the admin tree.
 746   *
 747   * Used to add a part_of_admin_tree object to this object or a child of this
 748   * object. $something should only be added if $destinationname matches
 749   * $this->name. If it doesn't, add should be called on child objects that are
 750   * also parentable_part_of_admin_tree's.
 751   *
 752   * $something should be appended as the last child in the $destinationname. If the
 753   * $beforesibling is specified, $something should be prepended to it. If the given
 754   * sibling is not found, $something should be appended to the end of $destinationname
 755   * and a developer debugging message should be displayed.
 756   *
 757   * @param string $destinationname The internal name of the new parent for $something.
 758   * @param part_of_admin_tree $something The object to be added.
 759   * @return bool True on success, false on failure.
 760   */
 761      public function add($destinationname, $something, $beforesibling = null);
 762  
 763  }
 764  
 765  
 766  /**
 767   * The object used to represent folders (a.k.a. categories) in the admin tree block.
 768   *
 769   * Each admin_category object contains a number of part_of_admin_tree objects.
 770   *
 771   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 772   */
 773  class admin_category implements parentable_part_of_admin_tree {
 774  
 775      /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */
 776      protected $children;
 777      /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
 778      public $name;
 779      /** @var string The displayed name for this category. Usually obtained through get_string() */
 780      public $visiblename;
 781      /** @var bool Should this category be hidden in admin tree block? */
 782      public $hidden;
 783      /** @var mixed Either a string or an array or strings */
 784      public $path;
 785      /** @var mixed Either a string or an array or strings */
 786      public $visiblepath;
 787  
 788      /** @var array fast lookup category cache, all categories of one tree point to one cache */
 789      protected $category_cache;
 790  
 791      /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */
 792      protected $sort = false;
 793      /** @var bool If set to true children will be sorted in ascending order. */
 794      protected $sortasc = true;
 795      /** @var bool If set to true sub categories and pages will be split and then sorted.. */
 796      protected $sortsplit = true;
 797      /** @var bool $sorted True if the children have been sorted and don't need resorting */
 798      protected $sorted = false;
 799  
 800      /**
 801       * Constructor for an empty admin category
 802       *
 803       * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
 804       * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
 805       * @param bool $hidden hide category in admin tree block, defaults to false
 806       */
 807      public function __construct($name, $visiblename, $hidden=false) {
 808          $this->children    = array();
 809          $this->name        = $name;
 810          $this->visiblename = $visiblename;
 811          $this->hidden      = $hidden;
 812      }
 813  
 814      /**
 815       * Get the URL to view this page.
 816       *
 817       * @return moodle_url
 818       */
 819      public function get_settings_page_url(): moodle_url {
 820          return new moodle_url(
 821              '/admin/category.php',
 822              [
 823                  'category' => $this->name,
 824              ]
 825          );
 826      }
 827  
 828      /**
 829       * Returns a reference to the part_of_admin_tree object with internal name $name.
 830       *
 831       * @param string $name The internal name of the object we want.
 832       * @param bool $findpath initialize path and visiblepath arrays
 833       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
 834       *                  defaults to false
 835       */
 836      public function locate($name, $findpath=false) {
 837          if (!isset($this->category_cache[$this->name])) {
 838              // somebody much have purged the cache
 839              $this->category_cache[$this->name] = $this;
 840          }
 841  
 842          if ($this->name == $name) {
 843              if ($findpath) {
 844                  $this->visiblepath[] = $this->visiblename;
 845                  $this->path[]        = $this->name;
 846              }
 847              return $this;
 848          }
 849  
 850          // quick category lookup
 851          if (!$findpath and isset($this->category_cache[$name])) {
 852              return $this->category_cache[$name];
 853          }
 854  
 855          $return = NULL;
 856          foreach($this->children as $childid=>$unused) {
 857              if ($return = $this->children[$childid]->locate($name, $findpath)) {
 858                  break;
 859              }
 860          }
 861  
 862          if (!is_null($return) and $findpath) {
 863              $return->visiblepath[] = $this->visiblename;
 864              $return->path[]        = $this->name;
 865          }
 866  
 867          return $return;
 868      }
 869  
 870      /**
 871       * Search using query
 872       *
 873       * @param string query
 874       * @return mixed array-object structure of found settings and pages
 875       */
 876      public function search($query) {
 877          $result = array();
 878          foreach ($this->get_children() as $child) {
 879              $subsearch = $child->search($query);
 880              if (!is_array($subsearch)) {
 881                  debugging('Incorrect search result from '.$child->name);
 882                  continue;
 883              }
 884              $result = array_merge($result, $subsearch);
 885          }
 886          return $result;
 887      }
 888  
 889      /**
 890       * Removes part_of_admin_tree object with internal name $name.
 891       *
 892       * @param string $name The internal name of the object we want to remove.
 893       * @return bool success
 894       */
 895      public function prune($name) {
 896  
 897          if ($this->name == $name) {
 898              return false;  //can not remove itself
 899          }
 900  
 901          foreach($this->children as $precedence => $child) {
 902              if ($child->name == $name) {
 903                  // clear cache and delete self
 904                  while($this->category_cache) {
 905                      // delete the cache, but keep the original array address
 906                      array_pop($this->category_cache);
 907                  }
 908                  unset($this->children[$precedence]);
 909                  return true;
 910              } else if ($this->children[$precedence]->prune($name)) {
 911                  return true;
 912              }
 913          }
 914          return false;
 915      }
 916  
 917      /**
 918       * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
 919       *
 920       * By default the new part of the tree is appended as the last child of the parent. You
 921       * can specify a sibling node that the new part should be prepended to. If the given
 922       * sibling is not found, the part is appended to the end (as it would be by default) and
 923       * a developer debugging message is displayed.
 924       *
 925       * @throws coding_exception if the $beforesibling is empty string or is not string at all.
 926       * @param string $destinationame The internal name of the immediate parent that we want for $something.
 927       * @param mixed $something A part_of_admin_tree or setting instance to be added.
 928       * @param string $beforesibling The name of the parent's child the $something should be prepended to.
 929       * @return bool True if successfully added, false if $something can not be added.
 930       */
 931      public function add($parentname, $something, $beforesibling = null) {
 932          global $CFG;
 933  
 934          $parent = $this->locate($parentname);
 935          if (is_null($parent)) {
 936              debugging('parent does not exist!');
 937              return false;
 938          }
 939  
 940          if ($something instanceof part_of_admin_tree) {
 941              if (!($parent instanceof parentable_part_of_admin_tree)) {
 942                  debugging('error - parts of tree can be inserted only into parentable parts');
 943                  return false;
 944              }
 945              if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
 946                  // The name of the node is already used, simply warn the developer that this should not happen.
 947                  // It is intentional to check for the debug level before performing the check.
 948                  debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
 949              }
 950              if (is_null($beforesibling)) {
 951                  // Append $something as the parent's last child.
 952                  $parent->children[] = $something;
 953              } else {
 954                  if (!is_string($beforesibling) or trim($beforesibling) === '') {
 955                      throw new coding_exception('Unexpected value of the beforesibling parameter');
 956                  }
 957                  // Try to find the position of the sibling.
 958                  $siblingposition = null;
 959                  foreach ($parent->children as $childposition => $child) {
 960                      if ($child->name === $beforesibling) {
 961                          $siblingposition = $childposition;
 962                          break;
 963                      }
 964                  }
 965                  if (is_null($siblingposition)) {
 966                      debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
 967                      $parent->children[] = $something;
 968                  } else {
 969                      $parent->children = array_merge(
 970                          array_slice($parent->children, 0, $siblingposition),
 971                          array($something),
 972                          array_slice($parent->children, $siblingposition)
 973                      );
 974                  }
 975              }
 976              if ($something instanceof admin_category) {
 977                  if (isset($this->category_cache[$something->name])) {
 978                      debugging('Duplicate admin category name: '.$something->name);
 979                  } else {
 980                      $this->category_cache[$something->name] = $something;
 981                      $something->category_cache =& $this->category_cache;
 982                      foreach ($something->children as $child) {
 983                          // just in case somebody already added subcategories
 984                          if ($child instanceof admin_category) {
 985                              if (isset($this->category_cache[$child->name])) {
 986                                  debugging('Duplicate admin category name: '.$child->name);
 987                              } else {
 988                                  $this->category_cache[$child->name] = $child;
 989                                  $child->category_cache =& $this->category_cache;
 990                              }
 991                          }
 992                      }
 993                  }
 994              }
 995              return true;
 996  
 997          } else {
 998              debugging('error - can not add this element');
 999              return false;
1000          }
1001  
1002      }
1003  
1004      /**
1005       * Checks if the user has access to anything in this category.
1006       *
1007       * @return bool True if the user has access to at least one child in this category, false otherwise.
1008       */
1009      public function check_access() {
1010          foreach ($this->children as $child) {
1011              if ($child->check_access()) {
1012                  return true;
1013              }
1014          }
1015          return false;
1016      }
1017  
1018      /**
1019       * Is this category hidden in admin tree block?
1020       *
1021       * @return bool True if hidden
1022       */
1023      public function is_hidden() {
1024          return $this->hidden;
1025      }
1026  
1027      /**
1028       * Show we display Save button at the page bottom?
1029       * @return bool
1030       */
1031      public function show_save() {
1032          foreach ($this->children as $child) {
1033              if ($child->show_save()) {
1034                  return true;
1035              }
1036          }
1037          return false;
1038      }
1039  
1040      /**
1041       * Sets sorting on this category.
1042       *
1043       * Please note this function doesn't actually do the sorting.
1044       * It can be called anytime.
1045       * Sorting occurs when the user calls get_children.
1046       * Code using the children array directly won't see the sorted results.
1047       *
1048       * @param bool $sort If set to true children will be sorted, if false they won't be.
1049       * @param bool $asc If true sorting will be ascending, otherwise descending.
1050       * @param bool $split If true we sort pages and sub categories separately.
1051       */
1052      public function set_sorting($sort, $asc = true, $split = true) {
1053          $this->sort = (bool)$sort;
1054          $this->sortasc = (bool)$asc;
1055          $this->sortsplit = (bool)$split;
1056      }
1057  
1058      /**
1059       * Returns the children associated with this category.
1060       *
1061       * @return part_of_admin_tree[]
1062       */
1063      public function get_children() {
1064          // If we should sort and it hasn't already been sorted.
1065          if ($this->sort && !$this->sorted) {
1066              if ($this->sortsplit) {
1067                  $categories = array();
1068                  $pages = array();
1069                  foreach ($this->children as $child) {
1070                      if ($child instanceof admin_category) {
1071                          $categories[] = $child;
1072                      } else {
1073                          $pages[] = $child;
1074                      }
1075                  }
1076                  core_collator::asort_objects_by_property($categories, 'visiblename');
1077                  core_collator::asort_objects_by_property($pages, 'visiblename');
1078                  if (!$this->sortasc) {
1079                      $categories = array_reverse($categories);
1080                      $pages = array_reverse($pages);
1081                  }
1082                  $this->children = array_merge($pages, $categories);
1083              } else {
1084                  core_collator::asort_objects_by_property($this->children, 'visiblename');
1085                  if (!$this->sortasc) {
1086                      $this->children = array_reverse($this->children);
1087                  }
1088              }
1089              $this->sorted = true;
1090          }
1091          return $this->children;
1092      }
1093  
1094      /**
1095       * Magically gets a property from this object.
1096       *
1097       * @param $property
1098       * @return part_of_admin_tree[]
1099       * @throws coding_exception
1100       */
1101      public function __get($property) {
1102          if ($property === 'children') {
1103              return $this->get_children();
1104          }
1105          throw new coding_exception('Invalid property requested.');
1106      }
1107  
1108      /**
1109       * Magically sets a property against this object.
1110       *
1111       * @param string $property
1112       * @param mixed $value
1113       * @throws coding_exception
1114       */
1115      public function __set($property, $value) {
1116          if ($property === 'children') {
1117              $this->sorted = false;
1118              $this->children = $value;
1119          } else {
1120              throw new coding_exception('Invalid property requested.');
1121          }
1122      }
1123  
1124      /**
1125       * Checks if an inaccessible property is set.
1126       *
1127       * @param string $property
1128       * @return bool
1129       * @throws coding_exception
1130       */
1131      public function __isset($property) {
1132          if ($property === 'children') {
1133              return isset($this->children);
1134          }
1135          throw new coding_exception('Invalid property requested.');
1136      }
1137  }
1138  
1139  
1140  /**
1141   * Root of admin settings tree, does not have any parent.
1142   *
1143   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1144   */
1145  class admin_root extends admin_category {
1146  /** @var array List of errors */
1147      public $errors;
1148      /** @var string search query */
1149      public $search;
1150      /** @var bool full tree flag - true means all settings required, false only pages required */
1151      public $fulltree;
1152      /** @var bool flag indicating loaded tree */
1153      public $loaded;
1154      /** @var mixed site custom defaults overriding defaults in settings files*/
1155      public $custom_defaults;
1156  
1157      /**
1158       * @param bool $fulltree true means all settings required,
1159       *                            false only pages required
1160       */
1161      public function __construct($fulltree) {
1162          global $CFG;
1163  
1164          parent::__construct('root', get_string('administration'), false);
1165          $this->errors   = array();
1166          $this->search   = '';
1167          $this->fulltree = $fulltree;
1168          $this->loaded   = false;
1169  
1170          $this->category_cache = array();
1171  
1172          // load custom defaults if found
1173          $this->custom_defaults = null;
1174          $defaultsfile = "$CFG->dirroot/local/defaults.php";
1175          if (is_readable($defaultsfile)) {
1176              $defaults = array();
1177              include($defaultsfile);
1178              if (is_array($defaults) and count($defaults)) {
1179                  $this->custom_defaults = $defaults;
1180              }
1181          }
1182      }
1183  
1184      /**
1185       * Empties children array, and sets loaded to false
1186       *
1187       * @param bool $requirefulltree
1188       */
1189      public function purge_children($requirefulltree) {
1190          $this->children = array();
1191          $this->fulltree = ($requirefulltree || $this->fulltree);
1192          $this->loaded   = false;
1193          //break circular dependencies - this helps PHP 5.2
1194          while($this->category_cache) {
1195              array_pop($this->category_cache);
1196          }
1197          $this->category_cache = array();
1198      }
1199  }
1200  
1201  
1202  /**
1203   * Links external PHP pages into the admin tree.
1204   *
1205   * See detailed usage example at the top of this document (adminlib.php)
1206   *
1207   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1208   */
1209  class admin_externalpage implements part_of_admin_tree {
1210  
1211      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1212      public $name;
1213  
1214      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1215      public $visiblename;
1216  
1217      /** @var string The external URL that we should link to when someone requests this external page. */
1218      public $url;
1219  
1220      /** @var array The role capability/permission a user must have to access this external page. */
1221      public $req_capability;
1222  
1223      /** @var object The context in which capability/permission should be checked, default is site context. */
1224      public $context;
1225  
1226      /** @var bool hidden in admin tree block. */
1227      public $hidden;
1228  
1229      /** @var mixed either string or array of string */
1230      public $path;
1231  
1232      /** @var array list of visible names of page parents */
1233      public $visiblepath;
1234  
1235      /**
1236       * Constructor for adding an external page into the admin tree.
1237       *
1238       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1239       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1240       * @param string $url The external URL that we should link to when someone requests this external page.
1241       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1242       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1243       * @param stdClass $context The context the page relates to. Not sure what happens
1244       *      if you specify something other than system or front page. Defaults to system.
1245       */
1246      public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1247          $this->name        = $name;
1248          $this->visiblename = $visiblename;
1249          $this->url         = $url;
1250          if (is_array($req_capability)) {
1251              $this->req_capability = $req_capability;
1252          } else {
1253              $this->req_capability = array($req_capability);
1254          }
1255          $this->hidden = $hidden;
1256          $this->context = $context;
1257      }
1258  
1259      /**
1260       * Returns a reference to the part_of_admin_tree object with internal name $name.
1261       *
1262       * @param string $name The internal name of the object we want.
1263       * @param bool $findpath defaults to false
1264       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1265       */
1266      public function locate($name, $findpath=false) {
1267          if ($this->name == $name) {
1268              if ($findpath) {
1269                  $this->visiblepath = array($this->visiblename);
1270                  $this->path        = array($this->name);
1271              }
1272              return $this;
1273          } else {
1274              $return = NULL;
1275              return $return;
1276          }
1277      }
1278  
1279      /**
1280       * This function always returns false, required function by interface
1281       *
1282       * @param string $name
1283       * @return false
1284       */
1285      public function prune($name) {
1286          return false;
1287      }
1288  
1289      /**
1290       * Search using query
1291       *
1292       * @param string $query
1293       * @return mixed array-object structure of found settings and pages
1294       */
1295      public function search($query) {
1296          $found = false;
1297          if (strpos(strtolower($this->name), $query) !== false) {
1298              $found = true;
1299          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1300                  $found = true;
1301              }
1302          if ($found) {
1303              $result = new stdClass();
1304              $result->page     = $this;
1305              $result->settings = array();
1306              return array($this->name => $result);
1307          } else {
1308              return array();
1309          }
1310      }
1311  
1312      /**
1313       * Determines if the current user has access to this external page based on $this->req_capability.
1314       *
1315       * @return bool True if user has access, false otherwise.
1316       */
1317      public function check_access() {
1318          global $CFG;
1319          $context = empty($this->context) ? context_system::instance() : $this->context;
1320          foreach($this->req_capability as $cap) {
1321              if (has_capability($cap, $context)) {
1322                  return true;
1323              }
1324          }
1325          return false;
1326      }
1327  
1328      /**
1329       * Is this external page hidden in admin tree block?
1330       *
1331       * @return bool True if hidden
1332       */
1333      public function is_hidden() {
1334          return $this->hidden;
1335      }
1336  
1337      /**
1338       * Show we display Save button at the page bottom?
1339       * @return bool
1340       */
1341      public function show_save() {
1342          return false;
1343      }
1344  }
1345  
1346  /**
1347   * Used to store details of the dependency between two settings elements.
1348   *
1349   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1350   * @copyright 2017 Davo Smith, Synergy Learning
1351   */
1352  class admin_settingdependency {
1353      /** @var string the name of the setting to be shown/hidden */
1354      public $settingname;
1355      /** @var string the setting this is dependent on */
1356      public $dependenton;
1357      /** @var string the condition to show/hide the element */
1358      public $condition;
1359      /** @var string the value to compare against */
1360      public $value;
1361  
1362      /** @var string[] list of valid conditions */
1363      private static $validconditions = ['checked', 'notchecked', 'noitemselected', 'eq', 'neq', 'in'];
1364  
1365      /**
1366       * admin_settingdependency constructor.
1367       * @param string $settingname
1368       * @param string $dependenton
1369       * @param string $condition
1370       * @param string $value
1371       * @throws \coding_exception
1372       */
1373      public function __construct($settingname, $dependenton, $condition, $value) {
1374          $this->settingname = $this->parse_name($settingname);
1375          $this->dependenton = $this->parse_name($dependenton);
1376          $this->condition = $condition;
1377          $this->value = $value;
1378  
1379          if (!in_array($this->condition, self::$validconditions)) {
1380              throw new coding_exception("Invalid condition '$condition'");
1381          }
1382      }
1383  
1384      /**
1385       * Convert the setting name into the form field name.
1386       * @param string $name
1387       * @return string
1388       */
1389      private function parse_name($name) {
1390          $bits = explode('/', $name);
1391          $name = array_pop($bits);
1392          $plugin = '';
1393          if ($bits) {
1394              $plugin = array_pop($bits);
1395              if ($plugin === 'moodle') {
1396                  $plugin = '';
1397              }
1398          }
1399          return 's_'.$plugin.'_'.$name;
1400      }
1401  
1402      /**
1403       * Gather together all the dependencies in a format suitable for initialising javascript
1404       * @param admin_settingdependency[] $dependencies
1405       * @return array
1406       */
1407      public static function prepare_for_javascript($dependencies) {
1408          $result = [];
1409          foreach ($dependencies as $d) {
1410              if (!isset($result[$d->dependenton])) {
1411                  $result[$d->dependenton] = [];
1412              }
1413              if (!isset($result[$d->dependenton][$d->condition])) {
1414                  $result[$d->dependenton][$d->condition] = [];
1415              }
1416              if (!isset($result[$d->dependenton][$d->condition][$d->value])) {
1417                  $result[$d->dependenton][$d->condition][$d->value] = [];
1418              }
1419              $result[$d->dependenton][$d->condition][$d->value][] = $d->settingname;
1420          }
1421          return $result;
1422      }
1423  }
1424  
1425  /**
1426   * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1427   *
1428   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1429   */
1430  class admin_settingpage implements part_of_admin_tree {
1431  
1432      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1433      public $name;
1434  
1435      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1436      public $visiblename;
1437  
1438      /** @var mixed An array of admin_setting objects that are part of this setting page. */
1439      public $settings;
1440  
1441      /** @var admin_settingdependency[] list of settings to hide when certain conditions are met */
1442      protected $dependencies = [];
1443  
1444      /** @var array The role capability/permission a user must have to access this external page. */
1445      public $req_capability;
1446  
1447      /** @var object The context in which capability/permission should be checked, default is site context. */
1448      public $context;
1449  
1450      /** @var bool hidden in admin tree block. */
1451      public $hidden;
1452  
1453      /** @var mixed string of paths or array of strings of paths */
1454      public $path;
1455  
1456      /** @var array list of visible names of page parents */
1457      public $visiblepath;
1458  
1459      /**
1460       * see admin_settingpage for details of this function
1461       *
1462       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1463       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1464       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1465       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1466       * @param stdClass $context The context the page relates to. Not sure what happens
1467       *      if you specify something other than system or front page. Defaults to system.
1468       */
1469      public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1470          $this->settings    = new stdClass();
1471          $this->name        = $name;
1472          $this->visiblename = $visiblename;
1473          if (is_array($req_capability)) {
1474              $this->req_capability = $req_capability;
1475          } else {
1476              $this->req_capability = array($req_capability);
1477          }
1478          $this->hidden      = $hidden;
1479          $this->context     = $context;
1480      }
1481  
1482      /**
1483       * see admin_category
1484       *
1485       * @param string $name
1486       * @param bool $findpath
1487       * @return mixed Object (this) if name ==  this->name, else returns null
1488       */
1489      public function locate($name, $findpath=false) {
1490          if ($this->name == $name) {
1491              if ($findpath) {
1492                  $this->visiblepath = array($this->visiblename);
1493                  $this->path        = array($this->name);
1494              }
1495              return $this;
1496          } else {
1497              $return = NULL;
1498              return $return;
1499          }
1500      }
1501  
1502      /**
1503       * Search string in settings page.
1504       *
1505       * @param string $query
1506       * @return array
1507       */
1508      public function search($query) {
1509          $found = array();
1510  
1511          foreach ($this->settings as $setting) {
1512              if ($setting->is_related($query)) {
1513                  $found[] = $setting;
1514              }
1515          }
1516  
1517          if ($found) {
1518              $result = new stdClass();
1519              $result->page     = $this;
1520              $result->settings = $found;
1521              return array($this->name => $result);
1522          }
1523  
1524          $found = false;
1525          if (strpos(strtolower($this->name), $query) !== false) {
1526              $found = true;
1527          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1528                  $found = true;
1529              }
1530          if ($found) {
1531              $result = new stdClass();
1532              $result->page     = $this;
1533              $result->settings = array();
1534              return array($this->name => $result);
1535          } else {
1536              return array();
1537          }
1538      }
1539  
1540      /**
1541       * This function always returns false, required by interface
1542       *
1543       * @param string $name
1544       * @return bool Always false
1545       */
1546      public function prune($name) {
1547          return false;
1548      }
1549  
1550      /**
1551       * adds an admin_setting to this admin_settingpage
1552       *
1553       * 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
1554       * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1555       *
1556       * @param object $setting is the admin_setting object you want to add
1557       * @return bool true if successful, false if not
1558       */
1559      public function add($setting) {
1560          if (!($setting instanceof admin_setting)) {
1561              debugging('error - not a setting instance');
1562              return false;
1563          }
1564  
1565          $name = $setting->name;
1566          if ($setting->plugin) {
1567              $name = $setting->plugin . $name;
1568          }
1569          $this->settings->{$name} = $setting;
1570          return true;
1571      }
1572  
1573      /**
1574       * Hide the named setting if the specified condition is matched.
1575       *
1576       * @param string $settingname
1577       * @param string $dependenton
1578       * @param string $condition
1579       * @param string $value
1580       */
1581      public function hide_if($settingname, $dependenton, $condition = 'notchecked', $value = '1') {
1582          $this->dependencies[] = new admin_settingdependency($settingname, $dependenton, $condition, $value);
1583  
1584          // Reformat the dependency name to the plugin | name format used in the display.
1585          $dependenton = str_replace('/', ' | ', $dependenton);
1586  
1587          // Let the setting know, so it can be displayed underneath.
1588          $findname = str_replace('/', '', $settingname);
1589          foreach ($this->settings as $name => $setting) {
1590              if ($name === $findname) {
1591                  $setting->add_dependent_on($dependenton);
1592              }
1593          }
1594      }
1595  
1596      /**
1597       * see admin_externalpage
1598       *
1599       * @return bool Returns true for yes false for no
1600       */
1601      public function check_access() {
1602          global $CFG;
1603          $context = empty($this->context) ? context_system::instance() : $this->context;
1604          foreach($this->req_capability as $cap) {
1605              if (has_capability($cap, $context)) {
1606                  return true;
1607              }
1608          }
1609          return false;
1610      }
1611  
1612      /**
1613       * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1614       * @return string Returns an XHTML string
1615       */
1616      public function output_html() {
1617          $adminroot = admin_get_root();
1618          $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1619          foreach($this->settings as $setting) {
1620              $fullname = $setting->get_full_name();
1621              if (array_key_exists($fullname, $adminroot->errors)) {
1622                  $data = $adminroot->errors[$fullname]->data;
1623              } else {
1624                  $data = $setting->get_setting();
1625                  // do not use defaults if settings not available - upgrade settings handles the defaults!
1626              }
1627              $return .= $setting->output_html($data);
1628          }
1629          $return .= '</fieldset>';
1630          return $return;
1631      }
1632  
1633      /**
1634       * Is this settings page hidden in admin tree block?
1635       *
1636       * @return bool True if hidden
1637       */
1638      public function is_hidden() {
1639          return $this->hidden;
1640      }
1641  
1642      /**
1643       * Show we display Save button at the page bottom?
1644       * @return bool
1645       */
1646      public function show_save() {
1647          foreach($this->settings as $setting) {
1648              if (empty($setting->nosave)) {
1649                  return true;
1650              }
1651          }
1652          return false;
1653      }
1654  
1655      /**
1656       * Should any of the settings on this page be shown / hidden based on conditions?
1657       * @return bool
1658       */
1659      public function has_dependencies() {
1660          return (bool)$this->dependencies;
1661      }
1662  
1663      /**
1664       * Format the setting show/hide conditions ready to initialise the page javascript
1665       * @return array
1666       */
1667      public function get_dependencies_for_javascript() {
1668          if (!$this->has_dependencies()) {
1669              return [];
1670          }
1671          return admin_settingdependency::prepare_for_javascript($this->dependencies);
1672      }
1673  }
1674  
1675  
1676  /**
1677   * Admin settings class. Only exists on setting pages.
1678   * Read & write happens at this level; no authentication.
1679   *
1680   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1681   */
1682  abstract class admin_setting {
1683      /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1684      public $name;
1685      /** @var string localised name */
1686      public $visiblename;
1687      /** @var string localised long description in Markdown format */
1688      public $description;
1689      /** @var mixed Can be string or array of string */
1690      public $defaultsetting;
1691      /** @var string */
1692      public $updatedcallback;
1693      /** @var mixed can be String or Null.  Null means main config table */
1694      public $plugin; // null means main config table
1695      /** @var bool true indicates this setting does not actually save anything, just information */
1696      public $nosave = false;
1697      /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1698      public $affectsmodinfo = false;
1699      /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */
1700      private $flags = array();
1701      /** @var bool Whether this field must be forced LTR. */
1702      private $forceltr = null;
1703      /** @var array list of other settings that may cause this setting to be hidden */
1704      private $dependenton = [];
1705      /** @var bool Whether this setting uses a custom form control */
1706      protected $customcontrol = false;
1707  
1708      /**
1709       * Constructor
1710       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1711       *                     or 'myplugin/mysetting' for ones in config_plugins.
1712       * @param string $visiblename localised name
1713       * @param string $description localised long description
1714       * @param mixed $defaultsetting string or array depending on implementation
1715       */
1716      public function __construct($name, $visiblename, $description, $defaultsetting) {
1717          $this->parse_setting_name($name);
1718          $this->visiblename    = $visiblename;
1719          $this->description    = $description;
1720          $this->defaultsetting = $defaultsetting;
1721      }
1722  
1723      /**
1724       * Generic function to add a flag to this admin setting.
1725       *
1726       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1727       * @param bool $default - The default for the flag
1728       * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name.
1729       * @param string $displayname - The display name for this flag. Used as a label next to the checkbox.
1730       */
1731      protected function set_flag_options($enabled, $default, $shortname, $displayname) {
1732          if (empty($this->flags[$shortname])) {
1733              $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname);
1734          } else {
1735              $this->flags[$shortname]->set_options($enabled, $default);
1736          }
1737      }
1738  
1739      /**
1740       * Set the enabled options flag on this admin setting.
1741       *
1742       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1743       * @param bool $default - The default for the flag
1744       */
1745      public function set_enabled_flag_options($enabled, $default) {
1746          $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin'));
1747      }
1748  
1749      /**
1750       * Set the advanced options flag on this admin setting.
1751       *
1752       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1753       * @param bool $default - The default for the flag
1754       */
1755      public function set_advanced_flag_options($enabled, $default) {
1756          $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced'));
1757      }
1758  
1759  
1760      /**
1761       * Set the locked options flag on this admin setting.
1762       *
1763       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1764       * @param bool $default - The default for the flag
1765       */
1766      public function set_locked_flag_options($enabled, $default) {
1767          $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
1768      }
1769  
1770      /**
1771       * Set the required options flag on this admin setting.
1772       *
1773       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED.
1774       * @param bool $default - The default for the flag.
1775       */
1776      public function set_required_flag_options($enabled, $default) {
1777          $this->set_flag_options($enabled, $default, 'required', new lang_string('required', 'core_admin'));
1778      }
1779  
1780      /**
1781       * Is this option forced in config.php?
1782       *
1783       * @return bool
1784       */
1785      public function is_readonly(): bool {
1786          global $CFG;
1787  
1788          if (empty($this->plugin)) {
1789              if (array_key_exists($this->name, $CFG->config_php_settings)) {
1790                  return true;
1791              }
1792          } else {
1793              if (array_key_exists($this->plugin, $CFG->forced_plugin_settings)
1794                  and array_key_exists($this->name, $CFG->forced_plugin_settings[$this->plugin])) {
1795                  return true;
1796              }
1797          }
1798          return false;
1799      }
1800  
1801      /**
1802       * Get the currently saved value for a setting flag
1803       *
1804       * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting.
1805       * @return bool
1806       */
1807      public function get_setting_flag_value(admin_setting_flag $flag) {
1808          $value = $this->config_read($this->name . '_' . $flag->get_shortname());
1809          if (!isset($value)) {
1810              $value = $flag->get_default();
1811          }
1812  
1813          return !empty($value);
1814      }
1815  
1816      /**
1817       * Get the list of defaults for the flags on this setting.
1818       *
1819       * @param array of strings describing the defaults for this setting. This is appended to by this function.
1820       */
1821      public function get_setting_flag_defaults(& $defaults) {
1822          foreach ($this->flags as $flag) {
1823              if ($flag->is_enabled() && $flag->get_default()) {
1824                  $defaults[] = $flag->get_displayname();
1825              }
1826          }
1827      }
1828  
1829      /**
1830       * Output the input fields for the advanced and locked flags on this setting.
1831       *
1832       * @param bool $adv - The current value of the advanced flag.
1833       * @param bool $locked - The current value of the locked flag.
1834       * @return string $output - The html for the flags.
1835       */
1836      public function output_setting_flags() {
1837          $output = '';
1838  
1839          foreach ($this->flags as $flag) {
1840              if ($flag->is_enabled()) {
1841                  $output .= $flag->output_setting_flag($this);
1842              }
1843          }
1844  
1845          if (!empty($output)) {
1846              return html_writer::tag('span', $output, array('class' => 'adminsettingsflags'));
1847          }
1848          return $output;
1849      }
1850  
1851      /**
1852       * Write the values of the flags for this admin setting.
1853       *
1854       * @param array $data - The data submitted from the form or null to set the default value for new installs.
1855       * @return bool - true if successful.
1856       */
1857      public function write_setting_flags($data) {
1858          $result = true;
1859          foreach ($this->flags as $flag) {
1860              $result = $result && $flag->write_setting_flag($this, $data);
1861          }
1862          return $result;
1863      }
1864  
1865      /**
1866       * Set up $this->name and potentially $this->plugin
1867       *
1868       * Set up $this->name and possibly $this->plugin based on whether $name looks
1869       * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1870       * on the names, that is, output a developer debug warning if the name
1871       * contains anything other than [a-zA-Z0-9_]+.
1872       *
1873       * @param string $name the setting name passed in to the constructor.
1874       */
1875      private function parse_setting_name($name) {
1876          $bits = explode('/', $name);
1877          if (count($bits) > 2) {
1878              throw new moodle_exception('invalidadminsettingname', '', '', $name);
1879          }
1880          $this->name = array_pop($bits);
1881          if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1882              throw new moodle_exception('invalidadminsettingname', '', '', $name);
1883          }
1884          if (!empty($bits)) {
1885              $this->plugin = array_pop($bits);
1886              if ($this->plugin === 'moodle') {
1887                  $this->plugin = null;
1888              } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1889                      throw new moodle_exception('invalidadminsettingname', '', '', $name);
1890                  }
1891          }
1892      }
1893  
1894      /**
1895       * Returns the fullname prefixed by the plugin
1896       * @return string
1897       */
1898      public function get_full_name() {
1899          return 's_'.$this->plugin.'_'.$this->name;
1900      }
1901  
1902      /**
1903       * Returns the ID string based on plugin and name
1904       * @return string
1905       */
1906      public function get_id() {
1907          return 'id_s_'.$this->plugin.'_'.$this->name;
1908      }
1909  
1910      /**
1911       * @param bool $affectsmodinfo If true, changes to this setting will
1912       *   cause the course cache to be rebuilt
1913       */
1914      public function set_affects_modinfo($affectsmodinfo) {
1915          $this->affectsmodinfo = $affectsmodinfo;
1916      }
1917  
1918      /**
1919       * Returns the config if possible
1920       *
1921       * @return mixed returns config if successful else null
1922       */
1923      public function config_read($name) {
1924          global $CFG;
1925          if (!empty($this->plugin)) {
1926              $value = get_config($this->plugin, $name);
1927              return $value === false ? NULL : $value;
1928  
1929          } else {
1930              if (isset($CFG->$name)) {
1931                  return $CFG->$name;
1932              } else {
1933                  return NULL;
1934              }
1935          }
1936      }
1937  
1938      /**
1939       * Used to set a config pair and log change
1940       *
1941       * @param string $name
1942       * @param mixed $value Gets converted to string if not null
1943       * @return bool Write setting to config table
1944       */
1945      public function config_write($name, $value) {
1946          global $DB, $USER, $CFG;
1947  
1948          if ($this->nosave) {
1949              return true;
1950          }
1951  
1952          // make sure it is a real change
1953          $oldvalue = get_config($this->plugin, $name);
1954          $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1955          $value = is_null($value) ? null : (string)$value;
1956  
1957          if ($oldvalue === $value) {
1958              return true;
1959          }
1960  
1961          // store change
1962          set_config($name, $value, $this->plugin);
1963  
1964          // Some admin settings affect course modinfo
1965          if ($this->affectsmodinfo) {
1966              // Clear course cache for all courses
1967              rebuild_course_cache(0, true);
1968          }
1969  
1970          $this->add_to_config_log($name, $oldvalue, $value);
1971  
1972          return true; // BC only
1973      }
1974  
1975      /**
1976       * Log config changes if necessary.
1977       * @param string $name
1978       * @param string $oldvalue
1979       * @param string $value
1980       */
1981      protected function add_to_config_log($name, $oldvalue, $value) {
1982          add_to_config_log($name, $oldvalue, $value, $this->plugin);
1983      }
1984  
1985      /**
1986       * Returns current value of this setting
1987       * @return mixed array or string depending on instance, NULL means not set yet
1988       */
1989      public abstract function get_setting();
1990  
1991      /**
1992       * Returns default setting if exists
1993       * @return mixed array or string depending on instance; NULL means no default, user must supply
1994       */
1995      public function get_defaultsetting() {
1996          $adminroot =  admin_get_root(false, false);
1997          if (!empty($adminroot->custom_defaults)) {
1998              $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1999              if (isset($adminroot->custom_defaults[$plugin])) {
2000                  if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
2001                      return $adminroot->custom_defaults[$plugin][$this->name];
2002                  }
2003              }
2004          }
2005          return $this->defaultsetting;
2006      }
2007  
2008      /**
2009       * Store new setting
2010       *
2011       * @param mixed $data string or array, must not be NULL
2012       * @return string empty string if ok, string error message otherwise
2013       */
2014      public abstract function write_setting($data);
2015  
2016      /**
2017       * Return part of form with setting
2018       * This function should always be overwritten
2019       *
2020       * @param mixed $data array or string depending on setting
2021       * @param string $query
2022       * @return string
2023       */
2024      public function output_html($data, $query='') {
2025      // should be overridden
2026          return;
2027      }
2028  
2029      /**
2030       * Function called if setting updated - cleanup, cache reset, etc.
2031       * @param string $functionname Sets the function name
2032       * @return void
2033       */
2034      public function set_updatedcallback($functionname) {
2035          $this->updatedcallback = $functionname;
2036      }
2037  
2038      /**
2039       * Execute postupdatecallback if necessary.
2040       * @param mixed $original original value before write_setting()
2041       * @return bool true if changed, false if not.
2042       */
2043      public function post_write_settings($original) {
2044          // Comparison must work for arrays too.
2045          if (serialize($original) === serialize($this->get_setting())) {
2046              return false;
2047          }
2048  
2049          $callbackfunction = $this->updatedcallback;
2050          if (!empty($callbackfunction) and is_callable($callbackfunction)) {
2051              $callbackfunction($this->get_full_name());
2052          }
2053          return true;
2054      }
2055  
2056      /**
2057       * Is setting related to query text - used when searching
2058       * @param string $query
2059       * @return bool
2060       */
2061      public function is_related($query) {
2062          if (strpos(strtolower($this->name), $query) !== false) {
2063              return true;
2064          }
2065          if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
2066              return true;
2067          }
2068          if (strpos(core_text::strtolower($this->description), $query) !== false) {
2069              return true;
2070          }
2071          $current = $this->get_setting();
2072          if (!is_null($current)) {
2073              if (is_string($current)) {
2074                  if (strpos(core_text::strtolower($current), $query) !== false) {
2075                      return true;
2076                  }
2077              }
2078          }
2079          $default = $this->get_defaultsetting();
2080          if (!is_null($default)) {
2081              if (is_string($default)) {
2082                  if (strpos(core_text::strtolower($default), $query) !== false) {
2083                      return true;
2084                  }
2085              }
2086          }
2087          return false;
2088      }
2089  
2090      /**
2091       * Get whether this should be displayed in LTR mode.
2092       *
2093       * @return bool|null
2094       */
2095      public function get_force_ltr() {
2096          return $this->forceltr;
2097      }
2098  
2099      /**
2100       * Set whether to force LTR or not.
2101       *
2102       * @param bool $value True when forced, false when not force, null when unknown.
2103       */
2104      public function set_force_ltr($value) {
2105          $this->forceltr = $value;
2106      }
2107  
2108      /**
2109       * Add a setting to the list of those that could cause this one to be hidden
2110       * @param string $dependenton
2111       */
2112      public function add_dependent_on($dependenton) {
2113          $this->dependenton[] = $dependenton;
2114      }
2115  
2116      /**
2117       * Get a list of the settings that could cause this one to be hidden.
2118       * @return array
2119       */
2120      public function get_dependent_on() {
2121          return $this->dependenton;
2122      }
2123  
2124      /**
2125       * Whether this setting uses a custom form control.
2126       * This function is especially useful to decide if we should render a label element for this setting or not.
2127       *
2128       * @return bool
2129       */
2130      public function has_custom_form_control(): bool {
2131          return $this->customcontrol;
2132      }
2133  }
2134  
2135  /**
2136   * An additional option that can be applied to an admin setting.
2137   * The currently supported options are 'ADVANCED', 'LOCKED' and 'REQUIRED'.
2138   *
2139   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2140   */
2141  class admin_setting_flag {
2142      /** @var bool Flag to indicate if this option can be toggled for this setting */
2143      private $enabled = false;
2144      /** @var bool Flag to indicate if this option defaults to true or false */
2145      private $default = false;
2146      /** @var string Short string used to create setting name - e.g. 'adv' */
2147      private $shortname = '';
2148      /** @var string String used as the label for this flag */
2149      private $displayname = '';
2150      /** @const Checkbox for this flag is displayed in admin page */
2151      const ENABLED = true;
2152      /** @const Checkbox for this flag is not displayed in admin page */
2153      const DISABLED = false;
2154  
2155      /**
2156       * Constructor
2157       *
2158       * @param bool $enabled Can this option can be toggled.
2159       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2160       * @param bool $default The default checked state for this setting option.
2161       * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
2162       * @param string $displayname The displayname of this flag. Used as a label for the flag.
2163       */
2164      public function __construct($enabled, $default, $shortname, $displayname) {
2165          $this->shortname = $shortname;
2166          $this->displayname = $displayname;
2167          $this->set_options($enabled, $default);
2168      }
2169  
2170      /**
2171       * Update the values of this setting options class
2172       *
2173       * @param bool $enabled Can this option can be toggled.
2174       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2175       * @param bool $default The default checked state for this setting option.
2176       */
2177      public function set_options($enabled, $default) {
2178          $this->enabled = $enabled;
2179          $this->default = $default;
2180      }
2181  
2182      /**
2183       * Should this option appear in the interface and be toggleable?
2184       *
2185       * @return bool Is it enabled?
2186       */
2187      public function is_enabled() {
2188          return $this->enabled;
2189      }
2190  
2191      /**
2192       * Should this option be checked by default?
2193       *
2194       * @return bool Is it on by default?
2195       */
2196      public function get_default() {
2197          return $this->default;
2198      }
2199  
2200      /**
2201       * Return the short name for this flag. e.g. 'adv' or 'locked'
2202       *
2203       * @return string
2204       */
2205      public function get_shortname() {
2206          return $this->shortname;
2207      }
2208  
2209      /**
2210       * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
2211       *
2212       * @return string
2213       */
2214      public function get_displayname() {
2215          return $this->displayname;
2216      }
2217  
2218      /**
2219       * Save the submitted data for this flag - or set it to the default if $data is null.
2220       *
2221       * @param admin_setting $setting - The admin setting for this flag
2222       * @param array $data - The data submitted from the form or null to set the default value for new installs.
2223       * @return bool
2224       */
2225      public function write_setting_flag(admin_setting $setting, $data) {
2226          $result = true;
2227          if ($this->is_enabled()) {
2228              if (!isset($data)) {
2229                  $value = $this->get_default();
2230              } else {
2231                  $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
2232              }
2233              $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
2234          }
2235  
2236          return $result;
2237  
2238      }
2239  
2240      /**
2241       * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
2242       *
2243       * @param admin_setting $setting - The admin setting for this flag
2244       * @return string - The html for the checkbox.
2245       */
2246      public function output_setting_flag(admin_setting $setting) {
2247          global $OUTPUT;
2248  
2249          $value = $setting->get_setting_flag_value($this);
2250  
2251          $context = new stdClass();
2252          $context->id = $setting->get_id() . '_' . $this->get_shortname();
2253          $context->name = $setting->get_full_name() .  '_' . $this->get_shortname();
2254          $context->value = 1;
2255          $context->checked = $value ? true : false;
2256          $context->label = $this->get_displayname();
2257  
2258          return $OUTPUT->render_from_template('core_admin/setting_flag', $context);
2259      }
2260  }
2261  
2262  
2263  /**
2264   * No setting - just heading and text.
2265   *
2266   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2267   */
2268  class admin_setting_heading extends admin_setting {
2269  
2270      /**
2271       * not a setting, just text
2272       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2273       * @param string $heading heading
2274       * @param string $information text in box
2275       */
2276      public function __construct($name, $heading, $information) {
2277          $this->nosave = true;
2278          parent::__construct($name, $heading, $information, '');
2279      }
2280  
2281      /**
2282       * Always returns true
2283       * @return bool Always returns true
2284       */
2285      public function get_setting() {
2286          return true;
2287      }
2288  
2289      /**
2290       * Always returns true
2291       * @return bool Always returns true
2292       */
2293      public function get_defaultsetting() {
2294          return true;
2295      }
2296  
2297      /**
2298       * Never write settings
2299       * @return string Always returns an empty string
2300       */
2301      public function write_setting($data) {
2302      // do not write any setting
2303          return '';
2304      }
2305  
2306      /**
2307       * Returns an HTML string
2308       * @return string Returns an HTML string
2309       */
2310      public function output_html($data, $query='') {
2311          global $OUTPUT;
2312          $context = new stdClass();
2313          $context->title = $this->visiblename;
2314          $context->description = $this->description;
2315          $context->descriptionformatted = highlight($query, markdown_to_html($this->description));
2316          return $OUTPUT->render_from_template('core_admin/setting_heading', $context);
2317      }
2318  }
2319  
2320  /**
2321   * No setting - just name and description in same row.
2322   *
2323   * @copyright 2018 onwards Amaia Anabitarte
2324   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2325   */
2326  class admin_setting_description extends admin_setting {
2327  
2328      /**
2329       * Not a setting, just text
2330       *
2331       * @param string $name
2332       * @param string $visiblename
2333       * @param string $description
2334       */
2335      public function __construct($name, $visiblename, $description) {
2336          $this->nosave = true;
2337          parent::__construct($name, $visiblename, $description, '');
2338      }
2339  
2340      /**
2341       * Always returns true
2342       *
2343       * @return bool Always returns true
2344       */
2345      public function get_setting() {
2346          return true;
2347      }
2348  
2349      /**
2350       * Always returns true
2351       *
2352       * @return bool Always returns true
2353       */
2354      public function get_defaultsetting() {
2355          return true;
2356      }
2357  
2358      /**
2359       * Never write settings
2360       *
2361       * @param mixed $data Gets converted to str for comparison against yes value
2362       * @return string Always returns an empty string
2363       */
2364      public function write_setting($data) {
2365          // Do not write any setting.
2366          return '';
2367      }
2368  
2369      /**
2370       * Returns an HTML string
2371       *
2372       * @param string $data
2373       * @param string $query
2374       * @return string Returns an HTML string
2375       */
2376      public function output_html($data, $query='') {
2377          global $OUTPUT;
2378  
2379          $context = new stdClass();
2380          $context->title = $this->visiblename;
2381          $context->description = $this->description;
2382  
2383          return $OUTPUT->render_from_template('core_admin/setting_description', $context);
2384      }
2385  }
2386  
2387  
2388  
2389  /**
2390   * The most flexible setting, the user enters text.
2391   *
2392   * This type of field should be used for config settings which are using
2393   * English words and are not localised (passwords, database name, list of values, ...).
2394   *
2395   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2396   */
2397  class admin_setting_configtext extends admin_setting {
2398  
2399      /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
2400      public $paramtype;
2401      /** @var int default field size */
2402      public $size;
2403  
2404      /**
2405       * Config text constructor
2406       *
2407       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2408       * @param string $visiblename localised
2409       * @param string $description long localised info
2410       * @param string $defaultsetting
2411       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2412       * @param int $size default field size
2413       */
2414      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2415          $this->paramtype = $paramtype;
2416          if (!is_null($size)) {
2417              $this->size  = $size;
2418          } else {
2419              $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
2420          }
2421          parent::__construct($name, $visiblename, $description, $defaultsetting);
2422      }
2423  
2424      /**
2425       * Get whether this should be displayed in LTR mode.
2426       *
2427       * Try to guess from the PARAM type unless specifically set.
2428       */
2429      public function get_force_ltr() {
2430          $forceltr = parent::get_force_ltr();
2431          if ($forceltr === null) {
2432              return !is_rtl_compatible($this->paramtype);
2433          }
2434          return $forceltr;
2435      }
2436  
2437      /**
2438       * Return the setting
2439       *
2440       * @return mixed returns config if successful else null
2441       */
2442      public function get_setting() {
2443          return $this->config_read($this->name);
2444      }
2445  
2446      public function write_setting($data) {
2447          if ($this->paramtype === PARAM_INT and $data === '') {
2448          // do not complain if '' used instead of 0
2449              $data = 0;
2450          }
2451          // $data is a string
2452          $validated = $this->validate($data);
2453          if ($validated !== true) {
2454              return $validated;
2455          }
2456          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2457      }
2458  
2459      /**
2460       * Validate data before storage
2461       * @param string data
2462       * @return mixed true if ok string if error found
2463       */
2464      public function validate($data) {
2465          // allow paramtype to be a custom regex if it is the form of /pattern/
2466          if (preg_match('#^/.*/$#', $this->paramtype)) {
2467              if (preg_match($this->paramtype, $data)) {
2468                  return true;
2469              } else {
2470                  return get_string('validateerror', 'admin');
2471              }
2472  
2473          } else if ($this->paramtype === PARAM_RAW) {
2474              return true;
2475  
2476          } else {
2477              $cleaned = clean_param($data, $this->paramtype);
2478              if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
2479                  return true;
2480              } else {
2481                  return get_string('validateerror', 'admin');
2482              }
2483          }
2484      }
2485  
2486      /**
2487       * Return an XHTML string for the setting
2488       * @return string Returns an XHTML string
2489       */
2490      public function output_html($data, $query='') {
2491          global $OUTPUT;
2492  
2493          $default = $this->get_defaultsetting();
2494          $context = (object) [
2495              'size' => $this->size,
2496              'id' => $this->get_id(),
2497              'name' => $this->get_full_name(),
2498              'value' => $data,
2499              'forceltr' => $this->get_force_ltr(),
2500              'readonly' => $this->is_readonly(),
2501          ];
2502          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
2503  
2504          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2505      }
2506  }
2507  
2508  /**
2509   * Text input with a maximum length constraint.
2510   *
2511   * @copyright 2015 onwards Ankit Agarwal
2512   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2513   */
2514  class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
2515  
2516      /** @var int maximum number of chars allowed. */
2517      protected $maxlength;
2518  
2519      /**
2520       * Config text constructor
2521       *
2522       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2523       *                     or 'myplugin/mysetting' for ones in config_plugins.
2524       * @param string $visiblename localised
2525       * @param string $description long localised info
2526       * @param string $defaultsetting
2527       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2528       * @param int $size default field size
2529       * @param mixed $maxlength int maxlength allowed, 0 for infinite.
2530       */
2531      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW,
2532                                  $size=null, $maxlength = 0) {
2533          $this->maxlength = $maxlength;
2534          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
2535      }
2536  
2537      /**
2538       * Validate data before storage
2539       *
2540       * @param string $data data
2541       * @return mixed true if ok string if error found
2542       */
2543      public function validate($data) {
2544          $parentvalidation = parent::validate($data);
2545          if ($parentvalidation === true) {
2546              if ($this->maxlength > 0) {
2547                  // Max length check.
2548                  $length = core_text::strlen($data);
2549                  if ($length > $this->maxlength) {
2550                      return get_string('maximumchars', 'moodle',  $this->maxlength);
2551                  }
2552                  return true;
2553              } else {
2554                  return true; // No max length check needed.
2555              }
2556          } else {
2557              return $parentvalidation;
2558          }
2559      }
2560  }
2561  
2562  /**
2563   * General text area without html editor.
2564   *
2565   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2566   */
2567  class admin_setting_configtextarea extends admin_setting_configtext {
2568      private $rows;
2569      private $cols;
2570  
2571      /**
2572       * @param string $name
2573       * @param string $visiblename
2574       * @param string $description
2575       * @param mixed $defaultsetting string or array
2576       * @param mixed $paramtype
2577       * @param string $cols The number of columns to make the editor
2578       * @param string $rows The number of rows to make the editor
2579       */
2580      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2581          $this->rows = $rows;
2582          $this->cols = $cols;
2583          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
2584      }
2585  
2586      /**
2587       * Returns an XHTML string for the editor
2588       *
2589       * @param string $data
2590       * @param string $query
2591       * @return string XHTML string for the editor
2592       */
2593      public function output_html($data, $query='') {
2594          global $OUTPUT;
2595  
2596          $default = $this->get_defaultsetting();
2597          $defaultinfo = $default;
2598          if (!is_null($default) and $default !== '') {
2599              $defaultinfo = "\n".$default;
2600          }
2601  
2602          $context = (object) [
2603              'cols' => $this->cols,
2604              'rows' => $this->rows,
2605              'id' => $this->get_id(),
2606              'name' => $this->get_full_name(),
2607              'value' => $data,
2608              'forceltr' => $this->get_force_ltr(),
2609              'readonly' => $this->is_readonly(),
2610          ];
2611          $element = $OUTPUT->render_from_template('core_admin/setting_configtextarea', $context);
2612  
2613          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
2614      }
2615  }
2616  
2617  /**
2618   * General text area with html editor.
2619   */
2620  class admin_setting_confightmleditor extends admin_setting_configtextarea {
2621  
2622      /**
2623       * @param string $name
2624       * @param string $visiblename
2625       * @param string $description
2626       * @param mixed $defaultsetting string or array
2627       * @param mixed $paramtype
2628       */
2629      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2630          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
2631          $this->set_force_ltr(false);
2632          editors_head_setup();
2633      }
2634  
2635      /**
2636       * Returns an XHTML string for the editor
2637       *
2638       * @param string $data
2639       * @param string $query
2640       * @return string XHTML string for the editor
2641       */
2642      public function output_html($data, $query='') {
2643          $editor = editors_get_preferred_editor(FORMAT_HTML);
2644          $editor->set_text($data);
2645          $editor->use_editor($this->get_id(), array('noclean'=>true));
2646          return parent::output_html($data, $query);
2647      }
2648  
2649      /**
2650       * Checks if data has empty html.
2651       *
2652       * @param string $data
2653       * @return string Empty when no errors.
2654       */
2655      public function write_setting($data) {
2656          if (trim(html_to_text($data)) === '') {
2657              $data = '';
2658          }
2659          return parent::write_setting($data);
2660      }
2661  }
2662  
2663  
2664  /**
2665   * Password field, allows unmasking of password
2666   *
2667   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2668   */
2669  class admin_setting_configpasswordunmask extends admin_setting_configtext {
2670  
2671      /**
2672       * Constructor
2673       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2674       * @param string $visiblename localised
2675       * @param string $description long localised info
2676       * @param string $defaultsetting default password
2677       */
2678      public function __construct($name, $visiblename, $description, $defaultsetting) {
2679          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2680      }
2681  
2682      /**
2683       * Log config changes if necessary.
2684       * @param string $name
2685       * @param string $oldvalue
2686       * @param string $value
2687       */
2688      protected function add_to_config_log($name, $oldvalue, $value) {
2689          if ($value !== '') {
2690              $value = '********';
2691          }
2692          if ($oldvalue !== '' and $oldvalue !== null) {
2693              $oldvalue = '********';
2694          }
2695          parent::add_to_config_log($name, $oldvalue, $value);
2696      }
2697  
2698      /**
2699       * Returns HTML for the field.
2700       *
2701       * @param   string  $data       Value for the field
2702       * @param   string  $query      Passed as final argument for format_admin_setting
2703       * @return  string              Rendered HTML
2704       */
2705      public function output_html($data, $query='') {
2706          global $OUTPUT;
2707  
2708          $context = (object) [
2709              'id' => $this->get_id(),
2710              'name' => $this->get_full_name(),
2711              'size' => $this->size,
2712              'value' => $this->is_readonly() ? null : $data,
2713              'forceltr' => $this->get_force_ltr(),
2714              'readonly' => $this->is_readonly(),
2715          ];
2716          $element = $OUTPUT->render_from_template('core_admin/setting_configpasswordunmask', $context);
2717          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', null, $query);
2718      }
2719  }
2720  
2721  /**
2722   * Password field, allows unmasking of password, with an advanced checkbox that controls an additional $name.'_adv' setting.
2723   *
2724   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2725   * @copyright 2018 Paul Holden (pholden@greenhead.ac.uk)
2726   */
2727  class admin_setting_configpasswordunmask_with_advanced extends admin_setting_configpasswordunmask {
2728  
2729      /**
2730       * Constructor
2731       *
2732       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2733       * @param string $visiblename localised
2734       * @param string $description long localised info
2735       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
2736       */
2737      public function __construct($name, $visiblename, $description, $defaultsetting) {
2738          parent::__construct($name, $visiblename, $description, $defaultsetting['value']);
2739          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
2740      }
2741  }
2742  
2743  /**
2744   * Admin setting class for encrypted values using secure encryption.
2745   *
2746   * @copyright 2019 The Open University
2747   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2748   */
2749  class admin_setting_encryptedpassword extends admin_setting {
2750  
2751      /**
2752       * Constructor. Same as parent except that the default value is always an empty string.
2753       *
2754       * @param string $name Internal name used in config table
2755       * @param string $visiblename Name shown on form
2756       * @param string $description Description that appears below field
2757       */
2758      public function __construct(string $name, string $visiblename, string $description) {
2759          parent::__construct($name, $visiblename, $description, '');
2760      }
2761  
2762      public function get_setting() {
2763          return $this->config_read($this->name);
2764      }
2765  
2766      public function write_setting($data) {
2767          $data = trim($data);
2768          if ($data === '') {
2769              // Value can really be set to nothing.
2770              $savedata = '';
2771          } else {
2772              // Encrypt value before saving it.
2773              $savedata = \core\encryption::encrypt($data);
2774          }
2775          return ($this->config_write($this->name, $savedata) ? '' : get_string('errorsetting', 'admin'));
2776      }
2777  
2778      public function output_html($data, $query='') {
2779          global $OUTPUT;
2780  
2781          $default = $this->get_defaultsetting();
2782          $context = (object) [
2783              'id' => $this->get_id(),
2784              'name' => $this->get_full_name(),
2785              'set' => $data !== '',
2786              'novalue' => $this->get_setting() === null
2787          ];
2788          $element = $OUTPUT->render_from_template('core_admin/setting_encryptedpassword', $context);
2789  
2790          return format_admin_setting($this, $this->visiblename, $element, $this->description,
2791                  true, '', $default, $query);
2792      }
2793  }
2794  
2795  /**
2796   * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
2797   * Note: Only advanced makes sense right now - locked does not.
2798   *
2799   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2800   */
2801  class admin_setting_configempty extends admin_setting_configtext {
2802  
2803      /**
2804       * @param string $name
2805       * @param string $visiblename
2806       * @param string $description
2807       */
2808      public function __construct($name, $visiblename, $description) {
2809          parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
2810      }
2811  
2812      /**
2813       * Returns an XHTML string for the hidden field
2814       *
2815       * @param string $data
2816       * @param string $query
2817       * @return string XHTML string for the editor
2818       */
2819      public function output_html($data, $query='') {
2820          global $OUTPUT;
2821  
2822          $context = (object) [
2823              'id' => $this->get_id(),
2824              'name' => $this->get_full_name()
2825          ];
2826          $element = $OUTPUT->render_from_template('core_admin/setting_configempty', $context);
2827  
2828          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', get_string('none'), $query);
2829      }
2830  }
2831  
2832  
2833  /**
2834   * Path to directory
2835   *
2836   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2837   */
2838  class admin_setting_configfile extends admin_setting_configtext {
2839      /**
2840       * Constructor
2841       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2842       * @param string $visiblename localised
2843       * @param string $description long localised info
2844       * @param string $defaultdirectory default directory location
2845       */
2846      public function __construct($name, $visiblename, $description, $defaultdirectory) {
2847          parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2848      }
2849  
2850      /**
2851       * Returns XHTML for the field
2852       *
2853       * Returns XHTML for the field and also checks whether the file
2854       * specified in $data exists using file_exists()
2855       *
2856       * @param string $data File name and path to use in value attr
2857       * @param string $query
2858       * @return string XHTML field
2859       */
2860      public function output_html($data, $query='') {
2861          global $CFG, $OUTPUT;
2862  
2863          $default = $this->get_defaultsetting();
2864          $context = (object) [
2865              'id' => $this->get_id(),
2866              'name' => $this->get_full_name(),
2867              'size' => $this->size,
2868              'value' => $data,
2869              'showvalidity' => !empty($data),
2870              'valid' => $data && file_exists($data),
2871              'readonly' => !empty($CFG->preventexecpath) || $this->is_readonly(),
2872              'forceltr' => $this->get_force_ltr(),
2873          ];
2874  
2875          if ($context->readonly) {
2876              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2877          }
2878  
2879          $element = $OUTPUT->render_from_template('core_admin/setting_configfile', $context);
2880  
2881          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2882      }
2883  
2884      /**
2885       * Checks if execpatch has been disabled in config.php
2886       */
2887      public function write_setting($data) {
2888          global $CFG;
2889          if (!empty($CFG->preventexecpath)) {
2890              if ($this->get_setting() === null) {
2891                  // Use default during installation.
2892                  $data = $this->get_defaultsetting();
2893                  if ($data === null) {
2894                      $data = '';
2895                  }
2896              } else {
2897                  return '';
2898              }
2899          }
2900          return parent::write_setting($data);
2901      }
2902  
2903  }
2904  
2905  
2906  /**
2907   * Path to executable file
2908   *
2909   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2910   */
2911  class admin_setting_configexecutable extends admin_setting_configfile {
2912  
2913      /**
2914       * Returns an XHTML field
2915       *
2916       * @param string $data This is the value for the field
2917       * @param string $query
2918       * @return string XHTML field
2919       */
2920      public function output_html($data, $query='') {
2921          global $CFG, $OUTPUT;
2922          $default = $this->get_defaultsetting();
2923          require_once("$CFG->libdir/filelib.php");
2924  
2925          $context = (object) [
2926              'id' => $this->get_id(),
2927              'name' => $this->get_full_name(),
2928              'size' => $this->size,
2929              'value' => $data,
2930              'showvalidity' => !empty($data),
2931              'valid' => $data && file_exists($data) && !is_dir($data) && file_is_executable($data),
2932              'readonly' => !empty($CFG->preventexecpath),
2933              'forceltr' => $this->get_force_ltr()
2934          ];
2935  
2936          if (!empty($CFG->preventexecpath)) {
2937              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2938          }
2939  
2940          $element = $OUTPUT->render_from_template('core_admin/setting_configexecutable', $context);
2941  
2942          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2943      }
2944  }
2945  
2946  
2947  /**
2948   * Path to directory
2949   *
2950   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2951   */
2952  class admin_setting_configdirectory extends admin_setting_configfile {
2953  
2954      /**
2955       * Returns an XHTML field
2956       *
2957       * @param string $data This is the value for the field
2958       * @param string $query
2959       * @return string XHTML
2960       */
2961      public function output_html($data, $query='') {
2962          global $CFG, $OUTPUT;
2963          $default = $this->get_defaultsetting();
2964  
2965          $context = (object) [
2966              'id' => $this->get_id(),
2967              'name' => $this->get_full_name(),
2968              'size' => $this->size,
2969              'value' => $data,
2970              'showvalidity' => !empty($data),
2971              'valid' => $data && file_exists($data) && is_dir($data),
2972              'readonly' => !empty($CFG->preventexecpath),
2973              'forceltr' => $this->get_force_ltr()
2974          ];
2975  
2976          if (!empty($CFG->preventexecpath)) {
2977              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2978          }
2979  
2980          $element = $OUTPUT->render_from_template('core_admin/setting_configdirectory', $context);
2981  
2982          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2983      }
2984  }
2985  
2986  
2987  /**
2988   * Checkbox
2989   *
2990   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2991   */
2992  class admin_setting_configcheckbox extends admin_setting {
2993      /** @var string Value used when checked */
2994      public $yes;
2995      /** @var string Value used when not checked */
2996      public $no;
2997  
2998      /**
2999       * Constructor
3000       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3001       * @param string $visiblename localised
3002       * @param string $description long localised info
3003       * @param string $defaultsetting
3004       * @param string $yes value used when checked
3005       * @param string $no value used when not checked
3006       */
3007      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
3008          parent::__construct($name, $visiblename, $description, $defaultsetting);
3009          $this->yes = (string)$yes;
3010          $this->no  = (string)$no;
3011      }
3012  
3013      /**
3014       * Retrieves the current setting using the objects name
3015       *
3016       * @return string
3017       */
3018      public function get_setting() {
3019          return $this->config_read($this->name);
3020      }
3021  
3022      /**
3023       * Sets the value for the setting
3024       *
3025       * Sets the value for the setting to either the yes or no values
3026       * of the object by comparing $data to yes
3027       *
3028       * @param mixed $data Gets converted to str for comparison against yes value
3029       * @return string empty string or error
3030       */
3031      public function write_setting($data) {
3032          if ((string)$data === $this->yes) { // convert to strings before comparison
3033              $data = $this->yes;
3034          } else {
3035              $data = $this->no;
3036          }
3037          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3038      }
3039  
3040      /**
3041       * Returns an XHTML checkbox field
3042       *
3043       * @param string $data If $data matches yes then checkbox is checked
3044       * @param string $query
3045       * @return string XHTML field
3046       */
3047      public function output_html($data, $query='') {
3048          global $OUTPUT;
3049  
3050          $context = (object) [
3051              'id' => $this->get_id(),
3052              'name' => $this->get_full_name(),
3053              'no' => $this->no,
3054              'value' => $this->yes,
3055              'checked' => (string) $data === $this->yes,
3056              'readonly' => $this->is_readonly(),
3057          ];
3058  
3059          $default = $this->get_defaultsetting();
3060          if (!is_null($default)) {
3061              if ((string)$default === $this->yes) {
3062                  $defaultinfo = get_string('checkboxyes', 'admin');
3063              } else {
3064                  $defaultinfo = get_string('checkboxno', 'admin');
3065              }
3066          } else {
3067              $defaultinfo = NULL;
3068          }
3069  
3070          $element = $OUTPUT->render_from_template('core_admin/setting_configcheckbox', $context);
3071  
3072          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3073      }
3074  }
3075  
3076  
3077  /**
3078   * Multiple checkboxes, each represents different value, stored in csv format
3079   *
3080   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3081   */
3082  class admin_setting_configmulticheckbox extends admin_setting {
3083      /** @var array Array of choices value=>label */
3084      public $choices;
3085      /** @var callable|null Loader function for choices */
3086      protected $choiceloader = null;
3087  
3088      /**
3089       * Constructor: uses parent::__construct
3090       *
3091       * The $choices parameter may be either an array of $value => $label format,
3092       * e.g. [1 => get_string('yes')], or a callback function which takes no parameters and
3093       * returns an array in that format.
3094       *
3095       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3096       * @param string $visiblename localised
3097       * @param string $description long localised info
3098       * @param array $defaultsetting array of selected
3099       * @param array|callable $choices array of $value => $label for each checkbox, or a callback
3100       */
3101      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3102          if (is_array($choices)) {
3103              $this->choices = $choices;
3104          }
3105          if (is_callable($choices)) {
3106              $this->choiceloader = $choices;
3107          }
3108          parent::__construct($name, $visiblename, $description, $defaultsetting);
3109      }
3110  
3111      /**
3112       * This function may be used in ancestors for lazy loading of choices
3113       *
3114       * Override this method if loading of choices is expensive, such
3115       * as when it requires multiple db requests.
3116       *
3117       * @return bool true if loaded, false if error
3118       */
3119      public function load_choices() {
3120          if ($this->choiceloader) {
3121              if (!is_array($this->choices)) {
3122                  $this->choices = call_user_func($this->choiceloader);
3123              }
3124          }
3125          return true;
3126      }
3127  
3128      /**
3129       * Is setting related to query text - used when searching
3130       *
3131       * @param string $query
3132       * @return bool true on related, false on not or failure
3133       */
3134      public function is_related($query) {
3135          if (!$this->load_choices() or empty($this->choices)) {
3136              return false;
3137          }
3138          if (parent::is_related($query)) {
3139              return true;
3140          }
3141  
3142          foreach ($this->choices as $desc) {
3143              if (strpos(core_text::strtolower($desc), $query) !== false) {
3144                  return true;
3145              }
3146          }
3147          return false;
3148      }
3149  
3150      /**
3151       * Returns the current setting if it is set
3152       *
3153       * @return mixed null if null, else an array
3154       */
3155      public function get_setting() {
3156          $result = $this->config_read($this->name);
3157  
3158          if (is_null($result)) {
3159              return NULL;
3160          }
3161          if ($result === '') {
3162              return array();
3163          }
3164          $enabled = explode(',', $result);
3165          $setting = array();
3166          foreach ($enabled as $option) {
3167              $setting[$option] = 1;
3168          }
3169          return $setting;
3170      }
3171  
3172      /**
3173       * Saves the setting(s) provided in $data
3174       *
3175       * @param array $data An array of data, if not array returns empty str
3176       * @return mixed empty string on useless data or bool true=success, false=failed
3177       */
3178      public function write_setting($data) {
3179          if (!is_array($data)) {
3180              return ''; // ignore it
3181          }
3182          if (!$this->load_choices() or empty($this->choices)) {
3183              return '';
3184          }
3185          unset($data['xxxxx']);
3186          $result = array();
3187          foreach ($data as $key => $value) {
3188              if ($value and array_key_exists($key, $this->choices)) {
3189                  $result[] = $key;
3190              }
3191          }
3192          return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
3193      }
3194  
3195      /**
3196       * Returns XHTML field(s) as required by choices
3197       *
3198       * Relies on data being an array should data ever be another valid vartype with
3199       * acceptable value this may cause a warning/error
3200       * if (!is_array($data)) would fix the problem
3201       *
3202       * @todo Add vartype handling to ensure $data is an array
3203       *
3204       * @param array $data An array of checked values
3205       * @param string $query
3206       * @return string XHTML field
3207       */
3208      public function output_html($data, $query='') {
3209          global $OUTPUT;
3210  
3211          if (!$this->load_choices() or empty($this->choices)) {
3212              return '';
3213          }
3214  
3215          $default = $this->get_defaultsetting();
3216          if (is_null($default)) {
3217              $default = array();
3218          }
3219          if (is_null($data)) {
3220              $data = array();
3221          }
3222  
3223          $context = (object) [
3224              'id' => $this->get_id(),
3225              'name' => $this->get_full_name(),
3226          ];
3227  
3228          $options = array();
3229          $defaults = array();
3230          foreach ($this->choices as $key => $description) {
3231              if (!empty($default[$key])) {
3232                  $defaults[] = $description;
3233              }
3234  
3235              $options[] = [
3236                  'key' => $key,
3237                  'checked' => !empty($data[$key]),
3238                  'label' => highlightfast($query, $description)
3239              ];
3240          }
3241  
3242          if (is_null($default)) {
3243              $defaultinfo = null;
3244          } else if (!empty($defaults)) {
3245              $defaultinfo = implode(', ', $defaults);
3246          } else {
3247              $defaultinfo = get_string('none');
3248          }
3249  
3250          $context->options = $options;
3251          $context->hasoptions = !empty($options);
3252  
3253          $element = $OUTPUT->render_from_template('core_admin/setting_configmulticheckbox', $context);
3254  
3255          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', $defaultinfo, $query);
3256  
3257      }
3258  }
3259  
3260  
3261  /**
3262   * Multiple checkboxes 2, value stored as string 00101011
3263   *
3264   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3265   */
3266  class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
3267  
3268      /**
3269       * Returns the setting if set
3270       *
3271       * @return mixed null if not set, else an array of set settings
3272       */
3273      public function get_setting() {
3274          $result = $this->config_read($this->name);
3275          if (is_null($result)) {
3276              return NULL;
3277          }
3278          if (!$this->load_choices()) {
3279              return NULL;
3280          }
3281          $result = str_pad($result, count($this->choices), '0');
3282          $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
3283          $setting = array();
3284          foreach ($this->choices as $key=>$unused) {
3285              $value = array_shift($result);
3286              if ($value) {
3287                  $setting[$key] = 1;
3288              }
3289          }
3290          return $setting;
3291      }
3292  
3293      /**
3294       * Save setting(s) provided in $data param
3295       *
3296       * @param array $data An array of settings to save
3297       * @return mixed empty string for bad data or bool true=>success, false=>error
3298       */
3299      public function write_setting($data) {
3300          if (!is_array($data)) {
3301              return ''; // ignore it
3302          }
3303          if (!$this->load_choices() or empty($this->choices)) {
3304              return '';
3305          }
3306          $result = '';
3307          foreach ($this->choices as $key=>$unused) {
3308              if (!empty($data[$key])) {
3309                  $result .= '1';
3310              } else {
3311                  $result .= '0';
3312              }
3313          }
3314          return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
3315      }
3316  }
3317  
3318  
3319  /**
3320   * Select one value from list
3321   *
3322   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3323   */
3324  class admin_setting_configselect extends admin_setting {
3325      /** @var array Array of choices value=>label */
3326      public $choices;
3327      /** @var array Array of choices grouped using optgroups */
3328      public $optgroups;
3329      /** @var callable|null Loader function for choices */
3330      protected $choiceloader = null;
3331      /** @var callable|null Validation function */
3332      protected $validatefunction = null;
3333  
3334      /**
3335       * Constructor.
3336       *
3337       * If you want to lazy-load the choices, pass a callback function that returns a choice
3338       * array for the $choices parameter.
3339       *
3340       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3341       * @param string $visiblename localised
3342       * @param string $description long localised info
3343       * @param string|int $defaultsetting
3344       * @param array|callable|null $choices array of $value=>$label for each selection, or callback
3345       */
3346      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3347          // Look for optgroup and single options.
3348          if (is_array($choices)) {
3349              $this->choices = [];
3350              foreach ($choices as $key => $val) {
3351                  if (is_array($val)) {
3352                      $this->optgroups[$key] = $val;
3353                      $this->choices = array_merge($this->choices, $val);
3354                  } else {
3355                      $this->choices[$key] = $val;
3356                  }
3357              }
3358          }
3359          if (is_callable($choices)) {
3360              $this->choiceloader = $choices;
3361          }
3362  
3363          parent::__construct($name, $visiblename, $description, $defaultsetting);
3364      }
3365  
3366      /**
3367       * Sets a validate function.
3368       *
3369       * The callback will be passed one parameter, the new setting value, and should return either
3370       * an empty string '' if the value is OK, or an error message if not.
3371       *
3372       * @param callable|null $validatefunction Validate function or null to clear
3373       * @since Moodle 3.10
3374       */
3375      public function set_validate_function(?callable $validatefunction = null) {
3376          $this->validatefunction = $validatefunction;
3377      }
3378  
3379      /**
3380       * This function may be used in ancestors for lazy loading of choices
3381       *
3382       * Override this method if loading of choices is expensive, such
3383       * as when it requires multiple db requests.
3384       *
3385       * @return bool true if loaded, false if error
3386       */
3387      public function load_choices() {
3388          if ($this->choiceloader) {
3389              if (!is_array($this->choices)) {
3390                  $this->choices = call_user_func($this->choiceloader);
3391              }
3392              return true;
3393          }
3394          return true;
3395      }
3396  
3397      /**
3398       * Check if this is $query is related to a choice
3399       *
3400       * @param string $query
3401       * @return bool true if related, false if not
3402       */
3403      public function is_related($query) {
3404          if (parent::is_related($query)) {
3405              return true;
3406          }
3407          if (!$this->load_choices()) {
3408              return false;
3409          }
3410          foreach ($this->choices as $key=>$value) {
3411              if (strpos(core_text::strtolower($key), $query) !== false) {
3412                  return true;
3413              }
3414              if (strpos(core_text::strtolower($value), $query) !== false) {
3415                  return true;
3416              }
3417          }
3418          return false;
3419      }
3420  
3421      /**
3422       * Return the setting
3423       *
3424       * @return mixed returns config if successful else null
3425       */
3426      public function get_setting() {
3427          return $this->config_read($this->name);
3428      }
3429  
3430      /**
3431       * Save a setting
3432       *
3433       * @param string $data
3434       * @return string empty of error string
3435       */
3436      public function write_setting($data) {
3437          if (!$this->load_choices() or empty($this->choices)) {
3438              return '';
3439          }
3440          if (!array_key_exists($data, $this->choices)) {
3441              return ''; // ignore it
3442          }
3443  
3444          // Validate the new setting.
3445          $error = $this->validate_setting($data);
3446          if ($error) {
3447              return $error;
3448          }
3449  
3450          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3451      }
3452  
3453      /**
3454       * Validate the setting. This uses the callback function if provided; subclasses could override
3455       * to carry out validation directly in the class.
3456       *
3457       * @param string $data New value being set
3458       * @return string Empty string if valid, or error message text
3459       * @since Moodle 3.10
3460       */
3461      protected function validate_setting(string $data): string {
3462          // If validation function is specified, call it now.
3463          if ($this->validatefunction) {
3464              return call_user_func($this->validatefunction, $data);
3465          } else {
3466              return '';
3467          }
3468      }
3469  
3470      /**
3471       * Returns XHTML select field
3472       *
3473       * Ensure the options are loaded, and generate the XHTML for the select
3474       * element and any warning message. Separating this out from output_html
3475       * makes it easier to subclass this class.
3476       *
3477       * @param string $data the option to show as selected.
3478       * @param string $current the currently selected option in the database, null if none.
3479       * @param string $default the default selected option.
3480       * @return array the HTML for the select element, and a warning message.
3481       * @deprecated since Moodle 3.2
3482       */
3483      public function output_select_html($data, $current, $default, $extraname = '') {
3484          debugging('The method admin_setting_configselect::output_select_html is depreacted, do not use any more.', DEBUG_DEVELOPER);
3485      }
3486  
3487      /**
3488       * Returns XHTML select field and wrapping div(s)
3489       *
3490       * @see output_select_html()
3491       *
3492       * @param string $data the option to show as selected
3493       * @param string $query
3494       * @return string XHTML field and wrapping div
3495       */
3496      public function output_html($data, $query='') {
3497          global $OUTPUT;
3498  
3499          $default = $this->get_defaultsetting();
3500          $current = $this->get_setting();
3501  
3502          if (!$this->load_choices() || empty($this->choices)) {
3503              return '';
3504          }
3505  
3506          $context = (object) [
3507              'id' => $this->get_id(),
3508              'name' => $this->get_full_name(),
3509          ];
3510  
3511          if (!is_null($default) && array_key_exists($default, $this->choices)) {
3512              $defaultinfo = $this->choices[$default];
3513          } else {
3514              $defaultinfo = NULL;
3515          }
3516  
3517          // Warnings.
3518          $warning = '';
3519          if ($current === null) {
3520              // First run.
3521          } else if (empty($current) && (array_key_exists('', $this->choices) || array_key_exists(0, $this->choices))) {
3522              // No warning.
3523          } else if (!array_key_exists($current, $this->choices)) {
3524              $warning = get_string('warningcurrentsetting', 'admin', $current);
3525              if (!is_null($default) && $data == $current) {
3526                  $data = $default; // Use default instead of first value when showing the form.
3527              }
3528          }
3529  
3530          $options = [];
3531          $template = 'core_admin/setting_configselect';
3532  
3533          if (!empty($this->optgroups)) {
3534              $optgroups = [];
3535              foreach ($this->optgroups as $label => $choices) {
3536                  $optgroup = array('label' => $label, 'options' => []);
3537                  foreach ($choices as $value => $name) {
3538                      $optgroup['options'][] = [
3539                          'value' => $value,
3540                          'name' => $name,
3541                          'selected' => (string) $value == $data
3542                      ];
3543                      unset($this->choices[$value]);
3544                  }
3545                  $optgroups[] = $optgroup;
3546              }
3547              $context->options = $options;
3548              $context->optgroups = $optgroups;
3549              $template = 'core_admin/setting_configselect_optgroup';
3550          }
3551  
3552          foreach ($this->choices as $value => $name) {
3553              $options[] = [
3554                  'value' => $value,
3555                  'name' => $name,
3556                  'selected' => (string) $value == $data
3557              ];
3558          }
3559          $context->options = $options;
3560          $context->readonly = $this->is_readonly();
3561  
3562          $element = $OUTPUT->render_from_template($template, $context);
3563  
3564          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, $warning, $defaultinfo, $query);
3565      }
3566  }
3567  
3568  /**
3569   * Select multiple items from list
3570   *
3571   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3572   */
3573  class admin_setting_configmultiselect extends admin_setting_configselect {
3574      /**
3575       * Constructor
3576       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3577       * @param string $visiblename localised
3578       * @param string $description long localised info
3579       * @param array $defaultsetting array of selected items
3580       * @param array $choices array of $value=>$label for each list item
3581       */
3582      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3583          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
3584      }
3585  
3586      /**
3587       * Returns the select setting(s)
3588       *
3589       * @return mixed null or array. Null if no settings else array of setting(s)
3590       */
3591      public function get_setting() {
3592          $result = $this->config_read($this->name);
3593          if (is_null($result)) {
3594              return NULL;
3595          }
3596          if ($result === '') {
3597              return array();
3598          }
3599          return explode(',', $result);
3600      }
3601  
3602      /**
3603       * Saves setting(s) provided through $data
3604       *
3605       * Potential bug in the works should anyone call with this function
3606       * using a vartype that is not an array
3607       *
3608       * @param array $data
3609       */
3610      public function write_setting($data) {
3611          if (!is_array($data)) {
3612              return ''; //ignore it
3613          }
3614          if (!$this->load_choices() or empty($this->choices)) {
3615              return '';
3616          }
3617  
3618          unset($data['xxxxx']);
3619  
3620          $save = array();
3621          foreach ($data as $value) {
3622              if (!array_key_exists($value, $this->choices)) {
3623                  continue; // ignore it
3624              }
3625              $save[] = $value;
3626          }
3627  
3628          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3629      }
3630  
3631      /**
3632       * Is setting related to query text - used when searching
3633       *
3634       * @param string $query
3635       * @return bool true if related, false if not
3636       */
3637      public function is_related($query) {
3638          if (!$this->load_choices() or empty($this->choices)) {
3639              return false;
3640          }
3641          if (parent::is_related($query)) {
3642              return true;
3643          }
3644  
3645          foreach ($this->choices as $desc) {
3646              if (strpos(core_text::strtolower($desc), $query) !== false) {
3647                  return true;
3648              }
3649          }
3650          return false;
3651      }
3652  
3653      /**
3654       * Returns XHTML multi-select field
3655       *
3656       * @todo Add vartype handling to ensure $data is an array
3657       * @param array $data Array of values to select by default
3658       * @param string $query
3659       * @return string XHTML multi-select field
3660       */
3661      public function output_html($data, $query='') {
3662          global $OUTPUT;
3663  
3664          if (!$this->load_choices() or empty($this->choices)) {
3665              return '';
3666          }
3667  
3668          $default = $this->get_defaultsetting();
3669          if (is_null($default)) {
3670              $default = array();
3671          }
3672          if (is_null($data)) {
3673              $data = array();
3674          }
3675  
3676          $context = (object) [
3677              'id' => $this->get_id(),
3678              'name' => $this->get_full_name(),
3679              'size' => min(10, count($this->choices))
3680          ];
3681  
3682          $defaults = [];
3683          $options = [];
3684          $template = 'core_admin/setting_configmultiselect';
3685  
3686          if (!empty($this->optgroups)) {
3687              $optgroups = [];
3688              foreach ($this->optgroups as $label => $choices) {
3689                  $optgroup = array('label' => $label, 'options' => []);
3690                  foreach ($choices as $value => $name) {
3691                      if (in_array($value, $default)) {
3692                          $defaults[] = $name;
3693                      }
3694                      $optgroup['options'][] = [
3695                          'value' => $value,
3696                          'name' => $name,
3697                          'selected' => in_array($value, $data)
3698                      ];
3699                      unset($this->choices[$value]);
3700                  }
3701                  $optgroups[] = $optgroup;
3702              }
3703              $context->optgroups = $optgroups;
3704              $template = 'core_admin/setting_configmultiselect_optgroup';
3705          }
3706  
3707          foreach ($this->choices as $value => $name) {
3708              if (in_array($value, $default)) {
3709                  $defaults[] = $name;
3710              }
3711              $options[] = [
3712                  'value' => $value,
3713                  'name' => $name,
3714                  'selected' => in_array($value, $data)
3715              ];
3716          }
3717          $context->options = $options;
3718          $context->readonly = $this->is_readonly();
3719  
3720          if (is_null($default)) {
3721              $defaultinfo = NULL;
3722          } if (!empty($defaults)) {
3723              $defaultinfo = implode(', ', $defaults);
3724          } else {
3725              $defaultinfo = get_string('none');
3726          }
3727  
3728          $element = $OUTPUT->render_from_template($template, $context);
3729  
3730          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3731      }
3732  }
3733  
3734  /**
3735   * Time selector
3736   *
3737   * This is a liiitle bit messy. we're using two selects, but we're returning
3738   * them as an array named after $name (so we only use $name2 internally for the setting)
3739   *
3740   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3741   */
3742  class admin_setting_configtime extends admin_setting {
3743      /** @var string Used for setting second select (minutes) */
3744      public $name2;
3745  
3746      /**
3747       * Constructor
3748       * @param string $hoursname setting for hours
3749       * @param string $minutesname setting for hours
3750       * @param string $visiblename localised
3751       * @param string $description long localised info
3752       * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3753       */
3754      public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
3755          $this->name2 = $minutesname;
3756          parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
3757      }
3758  
3759      /**
3760       * Get the selected time
3761       *
3762       * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
3763       */
3764      public function get_setting() {
3765          $result1 = $this->config_read($this->name);
3766          $result2 = $this->config_read($this->name2);
3767          if (is_null($result1) or is_null($result2)) {
3768              return NULL;
3769          }
3770  
3771          return array('h' => $result1, 'm' => $result2);
3772      }
3773  
3774      /**
3775       * Store the time (hours and minutes)
3776       *
3777       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3778       * @return bool true if success, false if not
3779       */
3780      public function write_setting($data) {
3781          if (!is_array($data)) {
3782              return '';
3783          }
3784  
3785          $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3786          return ($result ? '' : get_string('errorsetting', 'admin'));
3787      }
3788  
3789      /**
3790       * Returns XHTML time select fields
3791       *
3792       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3793       * @param string $query
3794       * @return string XHTML time select fields and wrapping div(s)
3795       */
3796      public function output_html($data, $query='') {
3797          global $OUTPUT;
3798  
3799          $default = $this->get_defaultsetting();
3800          if (is_array($default)) {
3801              $defaultinfo = $default['h'].':'.$default['m'];
3802          } else {
3803              $defaultinfo = NULL;
3804          }
3805  
3806          $context = (object) [
3807              'id' => $this->get_id(),
3808              'name' => $this->get_full_name(),
3809              'readonly' => $this->is_readonly(),
3810              'hours' => array_map(function($i) use ($data) {
3811                  return [
3812                      'value' => $i,
3813                      'name' => $i,
3814                      'selected' => $i == $data['h']
3815                  ];
3816              }, range(0, 23)),
3817              'minutes' => array_map(function($i) use ($data) {
3818                  return [
3819                      'value' => $i,
3820                      'name' => $i,
3821                      'selected' => $i == $data['m']
3822                  ];
3823              }, range(0, 59, 5))
3824          ];
3825  
3826          $element = $OUTPUT->render_from_template('core_admin/setting_configtime', $context);
3827  
3828          return format_admin_setting($this, $this->visiblename, $element, $this->description,
3829              $this->get_id() . 'h', '', $defaultinfo, $query);
3830      }
3831  
3832  }
3833  
3834  
3835  /**
3836   * Seconds duration setting.
3837   *
3838   * @copyright 2012 Petr Skoda (http://skodak.org)
3839   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3840   */
3841  class admin_setting_configduration extends admin_setting {
3842  
3843      /** @var int default duration unit */
3844      protected $defaultunit;
3845      /** @var callable|null Validation function */
3846      protected $validatefunction = null;
3847  
3848      /**
3849       * Constructor
3850       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3851       *                     or 'myplugin/mysetting' for ones in config_plugins.
3852       * @param string $visiblename localised name
3853       * @param string $description localised long description
3854       * @param mixed $defaultsetting string or array depending on implementation
3855       * @param int $defaultunit - day, week, etc. (in seconds)
3856       */
3857      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3858          if (is_number($defaultsetting)) {
3859              $defaultsetting = self::parse_seconds($defaultsetting);
3860          }
3861          $units = self::get_units();
3862          if (isset($units[$defaultunit])) {
3863              $this->defaultunit = $defaultunit;
3864          } else {
3865              $this->defaultunit = 86400;
3866          }
3867          parent::__construct($name, $visiblename, $description, $defaultsetting);
3868      }
3869  
3870      /**
3871       * Sets a validate function.
3872       *
3873       * The callback will be passed one parameter, the new setting value, and should return either
3874       * an empty string '' if the value is OK, or an error message if not.
3875       *
3876       * @param callable|null $validatefunction Validate function or null to clear
3877       * @since Moodle 3.10
3878       */
3879      public function set_validate_function(?callable $validatefunction = null) {
3880          $this->validatefunction = $validatefunction;
3881      }
3882  
3883      /**
3884       * Validate the setting. This uses the callback function if provided; subclasses could override
3885       * to carry out validation directly in the class.
3886       *
3887       * @param int $data New value being set
3888       * @return string Empty string if valid, or error message text
3889       * @since Moodle 3.10
3890       */
3891      protected function validate_setting(int $data): string {
3892          // If validation function is specified, call it now.
3893          if ($this->validatefunction) {
3894              return call_user_func($this->validatefunction, $data);
3895          } else {
3896              if ($data < 0) {
3897                  return get_string('errorsetting', 'admin');
3898              }
3899              return '';
3900          }
3901      }
3902  
3903      /**
3904       * Returns selectable units.
3905       * @static
3906       * @return array
3907       */
3908      protected static function get_units() {
3909          return array(
3910              604800 => get_string('weeks'),
3911              86400 => get_string('days'),
3912              3600 => get_string('hours'),
3913              60 => get_string('minutes'),
3914              1 => get_string('seconds'),
3915          );
3916      }
3917  
3918      /**
3919       * Converts seconds to some more user friendly string.
3920       * @static
3921       * @param int $seconds
3922       * @return string
3923       */
3924      protected static function get_duration_text($seconds) {
3925          if (empty($seconds)) {
3926              return get_string('none');
3927          }
3928          $data = self::parse_seconds($seconds);
3929          switch ($data['u']) {
3930              case (60*60*24*7):
3931                  return get_string('numweeks', '', $data['v']);
3932              case (60*60*24):
3933                  return get_string('numdays', '', $data['v']);
3934              case (60*60):
3935                  return get_string('numhours', '', $data['v']);
3936              case (60):
3937                  return get_string('numminutes', '', $data['v']);
3938              default:
3939                  return get_string('numseconds', '', $data['v']*$data['u']);
3940          }
3941      }
3942  
3943      /**
3944       * Finds suitable units for given duration.
3945       * @static
3946       * @param int $seconds
3947       * @return array
3948       */
3949      protected static function parse_seconds($seconds) {
3950          foreach (self::get_units() as $unit => $unused) {
3951              if ($seconds % $unit === 0) {
3952                  return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
3953              }
3954          }
3955          return array('v'=>(int)$seconds, 'u'=>1);
3956      }
3957  
3958      /**
3959       * Get the selected duration as array.
3960       *
3961       * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
3962       */
3963      public function get_setting() {
3964          $seconds = $this->config_read($this->name);
3965          if (is_null($seconds)) {
3966              return null;
3967          }
3968  
3969          return self::parse_seconds($seconds);
3970      }
3971  
3972      /**
3973       * Store the duration as seconds.
3974       *
3975       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3976       * @return bool true if success, false if not
3977       */
3978      public function write_setting($data) {
3979          if (!is_array($data)) {
3980              return '';
3981          }
3982  
3983          $unit = (int)$data['u'];
3984          $value = (int)$data['v'];
3985          $seconds = $value * $unit;
3986  
3987          // Validate the new setting.
3988          $error = $this->validate_setting($seconds);
3989          if ($error) {
3990              return $error;
3991          }
3992  
3993          $result = $this->config_write($this->name, $seconds);
3994          return ($result ? '' : get_string('errorsetting', 'admin'));
3995      }
3996  
3997      /**
3998       * Returns duration text+select fields.
3999       *
4000       * @param array $data Must be form 'v'=>xx, 'u'=>xx
4001       * @param string $query
4002       * @return string duration text+select fields and wrapping div(s)
4003       */
4004      public function output_html($data, $query='') {
4005          global $OUTPUT;
4006  
4007          $default = $this->get_defaultsetting();
4008          if (is_number($default)) {
4009              $defaultinfo = self::get_duration_text($default);
4010          } else if (is_array($default)) {
4011              $defaultinfo = self::get_duration_text($default['v']*$default['u']);
4012          } else {
4013              $defaultinfo = null;
4014          }
4015  
4016          $inputid = $this->get_id() . 'v';
4017          $units = self::get_units();
4018          $defaultunit = $this->defaultunit;
4019  
4020          $context = (object) [
4021              'id' => $this->get_id(),
4022              'name' => $this->get_full_name(),
4023              'value' => $data['v'],
4024              'readonly' => $this->is_readonly(),
4025              'options' => array_map(function($unit) use ($units, $data, $defaultunit) {
4026                  return [
4027                      'value' => $unit,
4028                      'name' => $units[$unit],
4029                      'selected' => ($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u']
4030                  ];
4031              }, array_keys($units))
4032          ];
4033  
4034          $element = $OUTPUT->render_from_template('core_admin/setting_configduration', $context);
4035  
4036          return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
4037      }
4038  }
4039  
4040  
4041  /**
4042   * Seconds duration setting with an advanced checkbox, that controls a additional
4043   * $name.'_adv' setting.
4044   *
4045   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4046   * @copyright 2014 The Open University
4047   */
4048  class admin_setting_configduration_with_advanced extends admin_setting_configduration {
4049      /**
4050       * Constructor
4051       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
4052       *                     or 'myplugin/mysetting' for ones in config_plugins.
4053       * @param string $visiblename localised name
4054       * @param string $description localised long description
4055       * @param array  $defaultsetting array of int value, and bool whether it is
4056       *                     is advanced by default.
4057       * @param int $defaultunit - day, week, etc. (in seconds)
4058       */
4059      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
4060          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $defaultunit);
4061          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
4062      }
4063  }
4064  
4065  
4066  /**
4067   * Used to validate a textarea used for ip addresses
4068   *
4069   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4070   * @copyright 2011 Petr Skoda (http://skodak.org)
4071   */
4072  class admin_setting_configiplist extends admin_setting_configtextarea {
4073  
4074      /**
4075       * Validate the contents of the textarea as IP addresses
4076       *
4077       * Used to validate a new line separated list of IP addresses collected from
4078       * a textarea control
4079       *
4080       * @param string $data A list of IP Addresses separated by new lines
4081       * @return mixed bool true for success or string:error on failure
4082       */
4083      public function validate($data) {
4084          if(!empty($data)) {
4085              $lines = explode("\n", $data);
4086          } else {
4087              return true;
4088          }
4089          $result = true;
4090          $badips = array();
4091          foreach ($lines as $line) {
4092              $tokens = explode('#', $line);
4093              $ip = trim($tokens[0]);
4094              if (empty($ip)) {
4095                  continue;
4096              }
4097              if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
4098                  preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
4099                  preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
4100              } else {
4101                  $result = false;
4102                  $badips[] = $ip;
4103              }
4104          }
4105          if($result) {
4106              return true;
4107          } else {
4108              return get_string('validateiperror', 'admin', join(', ', $badips));
4109          }
4110      }
4111  }
4112  
4113  /**
4114   * Used to validate a textarea used for domain names, wildcard domain names and IP addresses/ranges (both IPv4 and IPv6 format).
4115   *
4116   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4117   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4118   */
4119  class admin_setting_configmixedhostiplist extends admin_setting_configtextarea {
4120  
4121      /**
4122       * Validate the contents of the textarea as either IP addresses, domain name or wildcard domain name (RFC 4592).
4123       * Used to validate a new line separated list of entries collected from a textarea control.
4124       *
4125       * This setting provides support for internationalised domain names (IDNs), however, such UTF-8 names will be converted to
4126       * their ascii-compatible encoding (punycode) on save, and converted back to their UTF-8 representation when fetched
4127       * via the get_setting() method, which has been overriden.
4128       *
4129       * @param string $data A list of FQDNs, DNS wildcard format domains, and IP addresses, separated by new lines.
4130       * @return mixed bool true for success or string:error on failure
4131       */
4132      public function validate($data) {
4133          if (empty($data)) {
4134              return true;
4135          }
4136          $entries = explode("\n", $data);
4137          $badentries = [];
4138  
4139          foreach ($entries as $key => $entry) {
4140              $entry = trim($entry);
4141              if (empty($entry)) {
4142                  return get_string('validateemptylineerror', 'admin');
4143              }
4144  
4145              // Validate each string entry against the supported formats.
4146              if (\core\ip_utils::is_ip_address($entry) || \core\ip_utils::is_ipv6_range($entry)
4147                      || \core\ip_utils::is_ipv4_range($entry) || \core\ip_utils::is_domain_name($entry)
4148                      || \core\ip_utils::is_domain_matching_pattern($entry)) {
4149                  continue;
4150              }
4151  
4152              // Otherwise, the entry is invalid.
4153              $badentries[] = $entry;
4154          }
4155  
4156          if ($badentries) {
4157              return get_string('validateerrorlist', 'admin', join(', ', $badentries));
4158          }
4159          return true;
4160      }
4161  
4162      /**
4163       * Convert any lines containing international domain names (IDNs) to their ascii-compatible encoding (ACE).
4164       *
4165       * @param string $data the setting data, as sent from the web form.
4166       * @return string $data the setting data, with all IDNs converted (using punycode) to their ascii encoded version.
4167       */
4168      protected function ace_encode($data) {
4169          if (empty($data)) {
4170              return $data;
4171          }
4172          $entries = explode("\n", $data);
4173          foreach ($entries as $key => $entry) {
4174              $entry = trim($entry);
4175              // This regex matches any string that has non-ascii character.
4176              if (preg_match('/[^\x00-\x7f]/', $entry)) {
4177                  // If we can convert the unicode string to an idn, do so.
4178                  // Otherwise, leave the original unicode string alone and let the validation function handle it (it will fail).
4179                  $val = idn_to_ascii($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4180                  $entries[$key] = $val ? $val : $entry;
4181              }
4182          }
4183          return implode("\n", $entries);
4184      }
4185  
4186      /**
4187       * Decode any ascii-encoded domain names back to their utf-8 representation for display.
4188       *
4189       * @param string $data the setting data, as found in the database.
4190       * @return string $data the setting data, with all ascii-encoded IDNs decoded back to their utf-8 representation.
4191       */
4192      protected function ace_decode($data) {
4193          $entries = explode("\n", $data);
4194          foreach ($entries as $key => $entry) {
4195              $entry = trim($entry);
4196              if (strpos($entry, 'xn--') !== false) {
4197                  $entries[$key] = idn_to_utf8($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4198              }
4199          }
4200          return implode("\n", $entries);
4201      }
4202  
4203      /**
4204       * Override, providing utf8-decoding for ascii-encoded IDN strings.
4205       *
4206       * @return mixed returns punycode-converted setting string if successful, else null.
4207       */
4208      public function get_setting() {
4209          // Here, we need to decode any ascii-encoded IDNs back to their native, utf-8 representation.
4210          $data = $this->config_read($this->name);
4211          if (function_exists('idn_to_utf8') && !is_null($data)) {
4212              $data = $this->ace_decode($data);
4213          }
4214          return $data;
4215      }
4216  
4217      /**
4218       * Override, providing ascii-encoding for utf8 (native) IDN strings.
4219       *
4220       * @param string $data
4221       * @return string
4222       */
4223      public function write_setting($data) {
4224          if ($this->paramtype === PARAM_INT and $data === '') {
4225              // Do not complain if '' used instead of 0.
4226              $data = 0;
4227          }
4228  
4229          // Try to convert any non-ascii domains to ACE prior to validation - we can't modify anything in validate!
4230          if (function_exists('idn_to_ascii')) {
4231              $data = $this->ace_encode($data);
4232          }
4233  
4234          $validated = $this->validate($data);
4235          if ($validated !== true) {
4236              return $validated;
4237          }
4238          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
4239      }
4240  }
4241  
4242  /**
4243   * Used to validate a textarea used for port numbers.
4244   *
4245   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4246   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4247   */
4248  class admin_setting_configportlist extends admin_setting_configtextarea {
4249  
4250      /**
4251       * Validate the contents of the textarea as port numbers.
4252       * Used to validate a new line separated list of ports collected from a textarea control.
4253       *
4254       * @param string $data A list of ports separated by new lines
4255       * @return mixed bool true for success or string:error on failure
4256       */
4257      public function validate($data) {
4258          if (empty($data)) {
4259              return true;
4260          }
4261          $ports = explode("\n", $data);
4262          $badentries = [];
4263          foreach ($ports as $port) {
4264              $port = trim($port);
4265              if (empty($port)) {
4266                  return get_string('validateemptylineerror', 'admin');
4267              }
4268  
4269              // Is the string a valid integer number?
4270              if (strval(intval($port)) !== $port || intval($port) <= 0) {
4271                  $badentries[] = $port;
4272              }
4273          }
4274          if ($badentries) {
4275              return get_string('validateerrorlist', 'admin', $badentries);
4276          }
4277          return true;
4278      }
4279  }
4280  
4281  
4282  /**
4283   * An admin setting for selecting one or more users who have a capability
4284   * in the system context
4285   *
4286   * An admin setting for selecting one or more users, who have a particular capability
4287   * in the system context. Warning, make sure the list will never be too long. There is
4288   * no paging or searching of this list.
4289   *
4290   * To correctly get a list of users from this config setting, you need to call the
4291   * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
4292   *
4293   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4294   */
4295  class admin_setting_users_with_capability extends admin_setting_configmultiselect {
4296      /** @var string The capabilities name */
4297      protected $capability;
4298      /** @var int include admin users too */
4299      protected $includeadmins;
4300  
4301      /**
4302       * Constructor.
4303       *
4304       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
4305       * @param string $visiblename localised name
4306       * @param string $description localised long description
4307       * @param array $defaultsetting array of usernames
4308       * @param string $capability string capability name.
4309       * @param bool $includeadmins include administrators
4310       */
4311      function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
4312          $this->capability    = $capability;
4313          $this->includeadmins = $includeadmins;
4314          parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
4315      }
4316  
4317      /**
4318       * Load all of the uses who have the capability into choice array
4319       *
4320       * @return bool Always returns true
4321       */
4322      function load_choices() {
4323          if (is_array($this->choices)) {
4324              return true;
4325          }
4326          list($sort, $sortparams) = users_order_by_sql('u');
4327          if (!empty($sortparams)) {
4328              throw new coding_exception('users_order_by_sql returned some query parameters. ' .
4329                      'This is unexpected, and a problem because there is no way to pass these ' .
4330                      'parameters to get_users_by_capability. See MDL-34657.');
4331          }
4332          $userfieldsapi = \core_user\fields::for_name();
4333          $userfields = 'u.id, u.username, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects;
4334          $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort);
4335          $this->choices = array(
4336              '$@NONE@$' => get_string('nobody'),
4337              '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
4338          );
4339          if ($this->includeadmins) {
4340              $admins = get_admins();
4341              foreach ($admins as $user) {
4342                  $this->choices[$user->id] = fullname($user);
4343              }
4344          }
4345          if (is_array($users)) {
4346              foreach ($users as $user) {
4347                  $this->choices[$user->id] = fullname($user);
4348              }
4349          }
4350          return true;
4351      }
4352  
4353      /**
4354       * Returns the default setting for class
4355       *
4356       * @return mixed Array, or string. Empty string if no default
4357       */
4358      public function get_defaultsetting() {
4359          $this->load_choices();
4360          $defaultsetting = parent::get_defaultsetting();
4361          if (empty($defaultsetting)) {
4362              return array('$@NONE@$');
4363          } else if (array_key_exists($defaultsetting, $this->choices)) {
4364                  return $defaultsetting;
4365              } else {
4366                  return '';
4367              }
4368      }
4369  
4370      /**
4371       * Returns the current setting
4372       *
4373       * @return mixed array or string
4374       */
4375      public function get_setting() {
4376          $result = parent::get_setting();
4377          if ($result === null) {
4378              // this is necessary for settings upgrade
4379              return null;
4380          }
4381          if (empty($result)) {
4382              $result = array('$@NONE@$');
4383          }
4384          return $result;
4385      }
4386  
4387      /**
4388       * Save the chosen setting provided as $data
4389       *
4390       * @param array $data
4391       * @return mixed string or array
4392       */
4393      public function write_setting($data) {
4394      // If all is selected, remove any explicit options.
4395          if (in_array('$@ALL@$', $data)) {
4396              $data = array('$@ALL@$');
4397          }
4398          // None never needs to be written to the DB.
4399          if (in_array('$@NONE@$', $data)) {
4400              unset($data[array_search('$@NONE@$', $data)]);
4401          }
4402          return parent::write_setting($data);
4403      }
4404  }
4405  
4406  
4407  /**
4408   * Special checkbox for calendar - resets SESSION vars.
4409   *
4410   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4411   */
4412  class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
4413      /**
4414       * Calls the parent::__construct with default values
4415       *
4416       * name =>  calendar_adminseesall
4417       * visiblename => get_string('adminseesall', 'admin')
4418       * description => get_string('helpadminseesall', 'admin')
4419       * defaultsetting => 0
4420       */
4421      public function __construct() {
4422          parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
4423              get_string('helpadminseesall', 'admin'), '0');
4424      }
4425  
4426      /**
4427       * Stores the setting passed in $data
4428       *
4429       * @param mixed gets converted to string for comparison
4430       * @return string empty string or error message
4431       */
4432      public function write_setting($data) {
4433          global $SESSION;
4434          return parent::write_setting($data);
4435      }
4436  }
4437  
4438  /**
4439   * Special select for settings that are altered in setup.php and can not be altered on the fly
4440   *
4441   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4442   */
4443  class admin_setting_special_selectsetup extends admin_setting_configselect {
4444      /**
4445       * Reads the setting directly from the database
4446       *
4447       * @return mixed
4448       */
4449      public function get_setting() {
4450      // read directly from db!
4451          return get_config(NULL, $this->name);
4452      }
4453  
4454      /**
4455       * Save the setting passed in $data
4456       *
4457       * @param string $data The setting to save
4458       * @return string empty or error message
4459       */
4460      public function write_setting($data) {
4461          global $CFG;
4462          // do not change active CFG setting!
4463          $current = $CFG->{$this->name};
4464          $result = parent::write_setting($data);
4465          $CFG->{$this->name} = $current;
4466          return $result;
4467      }
4468  }
4469  
4470  
4471  /**
4472   * Special select for frontpage - stores data in course table
4473   *
4474   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4475   */
4476  class admin_setting_sitesetselect extends admin_setting_configselect {
4477      /**
4478       * Returns the site name for the selected site
4479       *
4480       * @see get_site()
4481       * @return string The site name of the selected site
4482       */
4483      public function get_setting() {
4484          $site = course_get_format(get_site())->get_course();
4485          return $site->{$this->name};
4486      }
4487  
4488      /**
4489       * Updates the database and save the setting
4490       *
4491       * @param string data
4492       * @return string empty or error message
4493       */
4494      public function write_setting($data) {
4495          global $DB, $SITE, $COURSE;
4496          if (!in_array($data, array_keys($this->choices))) {
4497              return get_string('errorsetting', 'admin');
4498          }
4499          $record = new stdClass();
4500          $record->id           = SITEID;
4501          $temp                 = $this->name;
4502          $record->$temp        = $data;
4503          $record->timemodified = time();
4504  
4505          course_get_format($SITE)->update_course_format_options($record);
4506          $DB->update_record('course', $record);
4507  
4508          // Reset caches.
4509          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4510          if ($SITE->id == $COURSE->id) {
4511              $COURSE = $SITE;
4512          }
4513          format_base::reset_course_cache($SITE->id);
4514  
4515          return '';
4516  
4517      }
4518  }
4519  
4520  
4521  /**
4522   * Select for blog's bloglevel setting: if set to 0, will set blog_menu
4523   * block to hidden.
4524   *
4525   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4526   */
4527  class admin_setting_bloglevel extends admin_setting_configselect {
4528      /**
4529       * Updates the database and save the setting
4530       *
4531       * @param string data
4532       * @return string empty or error message
4533       */
4534      public function write_setting($data) {
4535          global $DB, $CFG;
4536          if ($data == 0) {
4537              $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
4538              foreach ($blogblocks as $block) {
4539                  $DB->set_field('block', 'visible', 0, array('id' => $block->id));
4540              }
4541          } else {
4542              // reenable all blocks only when switching from disabled blogs
4543              if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
4544                  $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
4545                  foreach ($blogblocks as $block) {
4546                      $DB->set_field('block', 'visible', 1, array('id' => $block->id));
4547                  }
4548              }
4549          }
4550          return parent::write_setting($data);
4551      }
4552  }
4553  
4554  
4555  /**
4556   * Special select - lists on the frontpage - hacky
4557   *
4558   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4559   */
4560  class admin_setting_courselist_frontpage extends admin_setting {
4561      /** @var array Array of choices value=>label */
4562      public $choices;
4563  
4564      /**
4565       * Construct override, requires one param
4566       *
4567       * @param bool $loggedin Is the user logged in
4568       */
4569      public function __construct($loggedin) {
4570          global $CFG;
4571          require_once($CFG->dirroot.'/course/lib.php');
4572          $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
4573          $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
4574          $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
4575          $defaults    = array(FRONTPAGEALLCOURSELIST);
4576          parent::__construct($name, $visiblename, $description, $defaults);
4577      }
4578  
4579      /**
4580       * Loads the choices available
4581       *
4582       * @return bool always returns true
4583       */
4584      public function load_choices() {
4585          if (is_array($this->choices)) {
4586              return true;
4587          }
4588          $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
4589              FRONTPAGEALLCOURSELIST => get_string('frontpagecourselist'),
4590              FRONTPAGEENROLLEDCOURSELIST => get_string('frontpageenrolledcourselist'),
4591              FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
4592              FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
4593              FRONTPAGECOURSESEARCH  => get_string('frontpagecoursesearch'),
4594              'none'                 => get_string('none'));
4595          if ($this->name === 'frontpage') {
4596              unset($this->choices[FRONTPAGEENROLLEDCOURSELIST]);
4597          }
4598          return true;
4599      }
4600  
4601      /**
4602       * Returns the selected settings
4603       *
4604       * @param mixed array or setting or null
4605       */
4606      public function get_setting() {
4607          $result = $this->config_read($this->name);
4608          if (is_null($result)) {
4609              return NULL;
4610          }
4611          if ($result === '') {
4612              return array();
4613          }
4614          return explode(',', $result);
4615      }
4616  
4617      /**
4618       * Save the selected options
4619       *
4620       * @param array $data
4621       * @return mixed empty string (data is not an array) or bool true=success false=failure
4622       */
4623      public function write_setting($data) {
4624          if (!is_array($data)) {
4625              return '';
4626          }
4627          $this->load_choices();
4628          $save = array();
4629          foreach($data as $datum) {
4630              if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
4631                  continue;
4632              }
4633              $save[$datum] = $datum; // no duplicates
4634          }
4635          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
4636      }
4637  
4638      /**
4639       * Return XHTML select field and wrapping div
4640       *
4641       * @todo Add vartype handling to make sure $data is an array
4642       * @param array $data Array of elements to select by default
4643       * @return string XHTML select field and wrapping div
4644       */
4645      public function output_html($data, $query='') {
4646          global $OUTPUT;
4647  
4648          $this->load_choices();
4649          $currentsetting = array();
4650          foreach ($data as $key) {
4651              if ($key != 'none' and array_key_exists($key, $this->choices)) {
4652                  $currentsetting[] = $key; // already selected first
4653              }
4654          }
4655  
4656          $context = (object) [
4657              'id' => $this->get_id(),
4658              'name' => $this->get_full_name(),
4659          ];
4660  
4661          $options = $this->choices;
4662          $selects = [];
4663          for ($i = 0; $i < count($this->choices) - 1; $i++) {
4664              if (!array_key_exists($i, $currentsetting)) {
4665                  $currentsetting[$i] = 'none';
4666              }
4667              $selects[] = [
4668                  'key' => $i,
4669                  'options' => array_map(function($option) use ($options, $currentsetting, $i) {
4670                      return [
4671                          'name' => $options[$option],
4672                          'value' => $option,
4673                          'selected' => $currentsetting[$i] == $option
4674                      ];
4675                  }, array_keys($options))
4676              ];
4677          }
4678          $context->selects = $selects;
4679  
4680          $element = $OUTPUT->render_from_template('core_admin/setting_courselist_frontpage', $context);
4681  
4682          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
4683      }
4684  }
4685  
4686  
4687  /**
4688   * Special checkbox for frontpage - stores data in course table
4689   *
4690   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4691   */
4692  class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
4693      /**
4694       * Returns the current sites name
4695       *
4696       * @return string
4697       */
4698      public function get_setting() {
4699          $site = course_get_format(get_site())->get_course();
4700          return $site->{$this->name};
4701      }
4702  
4703      /**
4704       * Save the selected setting
4705       *
4706       * @param string $data The selected site
4707       * @return string empty string or error message
4708       */
4709      public function write_setting($data) {
4710          global $DB, $SITE, $COURSE;
4711          $record = new stdClass();
4712          $record->id            = $SITE->id;
4713          $record->{$this->name} = ($data == '1' ? 1 : 0);
4714          $record->timemodified  = time();
4715  
4716          course_get_format($SITE)->update_course_format_options($record);
4717          $DB->update_record('course', $record);
4718  
4719          // Reset caches.
4720          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4721          if ($SITE->id == $COURSE->id) {
4722              $COURSE = $SITE;
4723          }
4724          format_base::reset_course_cache($SITE->id);
4725  
4726          return '';
4727      }
4728  }
4729  
4730  /**
4731   * Special text for frontpage - stores data in course table.
4732   * Empty string means not set here. Manual setting is required.
4733   *
4734   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4735   */
4736  class admin_setting_sitesettext extends admin_setting_configtext {
4737  
4738      /**
4739       * Constructor.
4740       */
4741      public function __construct() {
4742          call_user_func_array(['parent', '__construct'], func_get_args());
4743          $this->set_force_ltr(false);
4744      }
4745  
4746      /**
4747       * Return the current setting
4748       *
4749       * @return mixed string or null
4750       */
4751      public function get_setting() {
4752          $site = course_get_format(get_site())->get_course();
4753          return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
4754      }
4755  
4756      /**
4757       * Validate the selected data
4758       *
4759       * @param string $data The selected value to validate
4760       * @return mixed true or message string
4761       */
4762      public function validate($data) {
4763          global $DB, $SITE;
4764          $cleaned = clean_param($data, PARAM_TEXT);
4765          if ($cleaned === '') {
4766              return get_string('required');
4767          }
4768          if ($this->name ==='shortname' &&
4769                  $DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data, $SITE->id))) {
4770              return get_string('shortnametaken', 'error', $data);
4771          }
4772          if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
4773              return true;
4774          } else {
4775              return get_string('validateerror', 'admin');
4776          }
4777      }
4778  
4779      /**
4780       * Save the selected setting
4781       *
4782       * @param string $data The selected value
4783       * @return string empty or error message
4784       */
4785      public function write_setting($data) {
4786          global $DB, $SITE, $COURSE;
4787          $data = trim($data);
4788          $validated = $this->validate($data);
4789          if ($validated !== true) {
4790              return $validated;
4791          }
4792  
4793          $record = new stdClass();
4794          $record->id            = $SITE->id;
4795          $record->{$this->name} = $data;
4796          $record->timemodified  = time();
4797  
4798          course_get_format($SITE)->update_course_format_options($record);
4799          $DB->update_record('course', $record);
4800  
4801          // Reset caches.
4802          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4803          if ($SITE->id == $COURSE->id) {
4804              $COURSE = $SITE;
4805          }
4806          format_base::reset_course_cache($SITE->id);
4807  
4808          return '';
4809      }
4810  }
4811  
4812  
4813  /**
4814   * Special text editor for site description.
4815   *
4816   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4817   */
4818  class admin_setting_special_frontpagedesc extends admin_setting_confightmleditor {
4819  
4820      /**
4821       * Calls parent::__construct with specific arguments
4822       */
4823      public function __construct() {
4824          parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), null,
4825              PARAM_RAW, 60, 15);
4826      }
4827  
4828      /**
4829       * Return the current setting
4830       * @return string The current setting
4831       */
4832      public function get_setting() {
4833          $site = course_get_format(get_site())->get_course();
4834          return $site->{$this->name};
4835      }
4836  
4837      /**
4838       * Save the new setting
4839       *
4840       * @param string $data The new value to save
4841       * @return string empty or error message
4842       */
4843      public function write_setting($data) {
4844          global $DB, $SITE, $COURSE;
4845          $record = new stdClass();
4846          $record->id            = $SITE->id;
4847          $record->{$this->name} = $data;
4848          $record->timemodified  = time();
4849  
4850          course_get_format($SITE)->update_course_format_options($record);
4851          $DB->update_record('course', $record);
4852  
4853          // Reset caches.
4854          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4855          if ($SITE->id == $COURSE->id) {
4856              $COURSE = $SITE;
4857          }
4858          format_base::reset_course_cache($SITE->id);
4859  
4860          return '';
4861      }
4862  }
4863  
4864  
4865  /**
4866   * Administration interface for emoticon_manager settings.
4867   *
4868   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4869   */
4870  class admin_setting_emoticons extends admin_setting {
4871  
4872      /**
4873       * Calls parent::__construct with specific args
4874       */
4875      public function __construct() {
4876          global $CFG;
4877  
4878          $manager = get_emoticon_manager();
4879          $defaults = $this->prepare_form_data($manager->default_emoticons());
4880          parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
4881      }
4882  
4883      /**
4884       * Return the current setting(s)
4885       *
4886       * @return array Current settings array
4887       */
4888      public function get_setting() {
4889          global $CFG;
4890  
4891          $manager = get_emoticon_manager();
4892  
4893          $config = $this->config_read($this->name);
4894          if (is_null($config)) {
4895              return null;
4896          }
4897  
4898          $config = $manager->decode_stored_config($config);
4899          if (is_null($config)) {
4900              return null;
4901          }
4902  
4903          return $this->prepare_form_data($config);
4904      }
4905  
4906      /**
4907       * Save selected settings
4908       *
4909       * @param array $data Array of settings to save
4910       * @return bool
4911       */
4912      public function write_setting($data) {
4913  
4914          $manager = get_emoticon_manager();
4915          $emoticons = $this->process_form_data($data);
4916  
4917          if ($emoticons === false) {
4918              return false;
4919          }
4920  
4921          if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
4922              return ''; // success
4923          } else {
4924              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
4925          }
4926      }
4927  
4928      /**
4929       * Return XHTML field(s) for options
4930       *
4931       * @param array $data Array of options to set in HTML
4932       * @return string XHTML string for the fields and wrapping div(s)
4933       */
4934      public function output_html($data, $query='') {
4935          global $OUTPUT;
4936  
4937          $context = (object) [
4938              'name' => $this->get_full_name(),
4939              'emoticons' => [],
4940              'forceltr' => true,
4941          ];
4942  
4943          $i = 0;
4944          foreach ($data as $field => $value) {
4945  
4946              // When $i == 0: text.
4947              // When $i == 1: imagename.
4948              // When $i == 2: imagecomponent.
4949              // When $i == 3: altidentifier.
4950              // When $i == 4: altcomponent.
4951              $fields[$i] = (object) [
4952                  'field' => $field,
4953                  'value' => $value,
4954                  'index' => $i
4955              ];
4956              $i++;
4957  
4958              if ($i > 4) {
4959                  $icon = null;
4960                  if (!empty($fields[1]->value)) {
4961                      if (get_string_manager()->string_exists($fields[3]->value, $fields[4]->value)) {
4962                          $alt = get_string($fields[3]->value, $fields[4]->value);
4963                      } else {
4964                          $alt = $fields[0]->value;
4965                      }
4966                      $icon = new pix_emoticon($fields[1]->value, $alt, $fields[2]->value);
4967                  }
4968                  $context->emoticons[] = [
4969                      'fields' => $fields,
4970                      'icon' => $icon ? $icon->export_for_template($OUTPUT) : null
4971                  ];
4972                  $fields = [];
4973                  $i = 0;
4974              }
4975          }
4976  
4977          $context->reseturl = new moodle_url('/admin/resetemoticons.php');
4978          $element = $OUTPUT->render_from_template('core_admin/setting_emoticons', $context);
4979          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
4980      }
4981  
4982      /**
4983       * Converts the array of emoticon objects provided by {@see emoticon_manager} into admin settings form data
4984       *
4985       * @see self::process_form_data()
4986       * @param array $emoticons array of emoticon objects as returned by {@see emoticon_manager}
4987       * @return array of form fields and their values
4988       */
4989      protected function prepare_form_data(array $emoticons) {
4990  
4991          $form = array();
4992          $i = 0;
4993          foreach ($emoticons as $emoticon) {
4994              $form['text'.$i]            = $emoticon->text;
4995              $form['imagename'.$i]       = $emoticon->imagename;
4996              $form['imagecomponent'.$i]  = $emoticon->imagecomponent;
4997              $form['altidentifier'.$i]   = $emoticon->altidentifier;
4998              $form['altcomponent'.$i]    = $emoticon->altcomponent;
4999              $i++;
5000          }
5001          // add one more blank field set for new object
5002          $form['text'.$i]            = '';
5003          $form['imagename'.$i]       = '';
5004          $form['imagecomponent'.$i]  = '';
5005          $form['altidentifier'.$i]   = '';
5006          $form['altcomponent'.$i]    = '';
5007  
5008          return $form;
5009      }
5010  
5011      /**
5012       * Converts the data from admin settings form into an array of emoticon objects
5013       *
5014       * @see self::prepare_form_data()
5015       * @param array $data array of admin form fields and values
5016       * @return false|array of emoticon objects
5017       */
5018      protected function process_form_data(array $form) {
5019  
5020          $count = count($form); // number of form field values
5021  
5022          if ($count % 5) {
5023              // we must get five fields per emoticon object
5024              return false;
5025          }
5026  
5027          $emoticons = array();
5028          for ($i = 0; $i < $count / 5; $i++) {
5029              $emoticon                   = new stdClass();
5030              $emoticon->text             = clean_param(trim($form['text'.$i]), PARAM_NOTAGS);
5031              $emoticon->imagename        = clean_param(trim($form['imagename'.$i]), PARAM_PATH);
5032              $emoticon->imagecomponent   = clean_param(trim($form['imagecomponent'.$i]), PARAM_COMPONENT);
5033              $emoticon->altidentifier    = clean_param(trim($form['altidentifier'.$i]), PARAM_STRINGID);
5034              $emoticon->altcomponent     = clean_param(trim($form['altcomponent'.$i]), PARAM_COMPONENT);
5035  
5036              if (strpos($emoticon->text, ':/') !== false or strpos($emoticon->text, '//') !== false) {
5037                  // prevent from breaking http://url.addresses by accident
5038                  $emoticon->text = '';
5039              }
5040  
5041              if (strlen($emoticon->text) < 2) {
5042                  // do not allow single character emoticons
5043                  $emoticon->text = '';
5044              }
5045  
5046              if (preg_match('/^[a-zA-Z]+[a-zA-Z0-9]*$/', $emoticon->text)) {
5047                  // emoticon text must contain some non-alphanumeric character to prevent
5048                  // breaking HTML tags
5049                  $emoticon->text = '';
5050              }
5051  
5052              if ($emoticon->text !== '' and $emoticon->imagename !== '' and $emoticon->imagecomponent !== '') {
5053                  $emoticons[] = $emoticon;
5054              }
5055          }
5056          return $emoticons;
5057      }
5058  
5059  }
5060  
5061  
5062  /**
5063   * Special setting for limiting of the list of available languages.
5064   *
5065   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5066   */
5067  class admin_setting_langlist extends admin_setting_configtext {
5068      /**
5069       * Calls parent::__construct with specific arguments
5070       */
5071      public function __construct() {
5072          parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
5073      }
5074  
5075      /**
5076       * Validate that each language identifier exists on the site
5077       *
5078       * @param string $data
5079       * @return bool|string True if validation successful, otherwise error string
5080       */
5081      public function validate($data) {
5082          $parentcheck = parent::validate($data);
5083          if ($parentcheck !== true) {
5084              return $parentcheck;
5085          }
5086  
5087          if ($data === '') {
5088              return true;
5089          }
5090  
5091          // Normalize language identifiers.
5092          $langcodes = array_map('trim', explode(',', $data));
5093          foreach ($langcodes as $langcode) {
5094              // If the langcode contains optional alias, split it out.
5095              [$langcode, ] = preg_split('/\s*\|\s*/', $langcode, 2);
5096  
5097              if (!get_string_manager()->translation_exists($langcode)) {
5098                  return get_string('invalidlanguagecode', 'error', $langcode);
5099              }
5100          }
5101  
5102          return true;
5103      }
5104  
5105      /**
5106       * Save the new setting
5107       *
5108       * @param string $data The new setting
5109       * @return bool
5110       */
5111      public function write_setting($data) {
5112          $return = parent::write_setting($data);
5113          get_string_manager()->reset_caches();
5114          return $return;
5115      }
5116  }
5117  
5118  
5119  /**
5120   * Allows to specify comma separated list of known country codes.
5121   *
5122   * This is a simple subclass of the plain input text field with added validation so that all the codes are actually
5123   * known codes.
5124   *
5125   * @package     core
5126   * @category    admin
5127   * @copyright   2020 David Mudrák <david@moodle.com>
5128   * @license     https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5129   */
5130  class admin_setting_countrycodes extends admin_setting_configtext {
5131  
5132      /**
5133       * Construct the instance of the setting.
5134       *
5135       * @param string $name Name of the admin setting such as 'allcountrycodes' or 'myplugin/countries'.
5136       * @param lang_string|string $visiblename Language string with the field label text.
5137       * @param lang_string|string $description Language string with the field description text.
5138       * @param string $defaultsetting Default value of the setting.
5139       * @param int $size Input text field size.
5140       */
5141      public function __construct($name, $visiblename, $description, $defaultsetting = '', $size = null) {
5142          parent::__construct($name, $visiblename, $description, $defaultsetting, '/^(?:\w+(?:,\w+)*)?$/', $size);
5143      }
5144  
5145      /**
5146       * Validate the setting value before storing it.
5147       *
5148       * The value is first validated through custom regex so that it is a word consisting of letters, numbers or underscore; or
5149       * a comma separated list of such words.
5150       *
5151       * @param string $data Value inserted into the setting field.
5152       * @return bool|string True if the value is OK, error string otherwise.
5153       */
5154      public function validate($data) {
5155  
5156          $parentcheck = parent::validate($data);
5157  
5158          if ($parentcheck !== true) {
5159              return $parentcheck;
5160          }
5161  
5162          if ($data === '') {
5163              return true;
5164          }
5165  
5166          $allcountries = get_string_manager()->get_list_of_countries(true);
5167  
5168          foreach (explode(',', $data) as $code) {
5169              if (!isset($allcountries[$code])) {
5170                  return get_string('invalidcountrycode', 'core_error', $code);
5171              }
5172          }
5173  
5174          return true;
5175      }
5176  }
5177  
5178  
5179  /**
5180   * Selection of one of the recognised countries using the list
5181   * returned by {@link get_list_of_countries()}.
5182   *
5183   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5184   */
5185  class admin_settings_country_select extends admin_setting_configselect {
5186      protected $includeall;
5187      public function __construct($name, $visiblename, $description, $defaultsetting, $includeall=false) {
5188          $this->includeall = $includeall;
5189          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
5190      }
5191  
5192      /**
5193       * Lazy-load the available choices for the select box
5194       */
5195      public function load_choices() {
5196          global $CFG;
5197          if (is_array($this->choices)) {
5198              return true;
5199          }
5200          $this->choices = array_merge(
5201                  array('0' => get_string('choosedots')),
5202                  get_string_manager()->get_list_of_countries($this->includeall));
5203          return true;
5204      }
5205  }
5206  
5207  
5208  /**
5209   * admin_setting_configselect for the default number of sections in a course,
5210   * simply so we can lazy-load the choices.
5211   *
5212   * @copyright 2011 The Open University
5213   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5214   */
5215  class admin_settings_num_course_sections extends admin_setting_configselect {
5216      public function __construct($name, $visiblename, $description, $defaultsetting) {
5217          parent::__construct($name, $visiblename, $description, $defaultsetting, array());
5218      }
5219  
5220      /** Lazy-load the available choices for the select box */
5221      public function load_choices() {
5222          $max = get_config('moodlecourse', 'maxsections');
5223          if (!isset($max) || !is_numeric($max)) {
5224              $max = 52;
5225          }
5226          for ($i = 0; $i <= $max; $i++) {
5227              $this->choices[$i] = "$i";
5228          }
5229          return true;
5230      }
5231  }
5232  
5233  
5234  /**
5235   * Course category selection
5236   *
5237   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5238   */
5239  class admin_settings_coursecat_select extends admin_setting_configselect_autocomplete {
5240      /**
5241       * Calls parent::__construct with specific arguments
5242       */
5243      public function __construct($name, $visiblename, $description, $defaultsetting = 1) {
5244          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices = null);
5245      }
5246  
5247      /**
5248       * Load the available choices for the select box
5249       *
5250       * @return bool
5251       */
5252      public function load_choices() {
5253          if (is_array($this->choices)) {
5254              return true;
5255          }
5256          $this->choices = core_course_category::make_categories_list('', 0, ' / ');
5257          return true;
5258      }
5259  }
5260  
5261  
5262  /**
5263   * Special control for selecting days to backup
5264   *
5265   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5266   */
5267  class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
5268      /**
5269       * Calls parent::__construct with specific arguments
5270       */
5271      public function __construct() {
5272          parent::__construct('backup_auto_weekdays', get_string('automatedbackupschedule','backup'), get_string('automatedbackupschedulehelp','backup'), array(), NULL);
5273          $this->plugin = 'backup';
5274      }
5275  
5276      /**
5277       * Load the available choices for the select box
5278       *
5279       * @return bool Always returns true
5280       */
5281      public function load_choices() {
5282          if (is_array($this->choices)) {
5283              return true;
5284          }
5285          $this->choices = array();
5286          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5287          foreach ($days as $day) {
5288              $this->choices[$day] = get_string($day, 'calendar');
5289          }
5290          return true;
5291      }
5292  }
5293  
5294  /**
5295   * Special setting for backup auto destination.
5296   *
5297   * @package    core
5298   * @subpackage admin
5299   * @copyright  2014 Frédéric Massart - FMCorz.net
5300   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5301   */
5302  class admin_setting_special_backup_auto_destination extends admin_setting_configdirectory {
5303  
5304      /**
5305       * Calls parent::__construct with specific arguments.
5306       */
5307      public function __construct() {
5308          parent::__construct('backup/backup_auto_destination', new lang_string('saveto'), new lang_string('backupsavetohelp'), '');
5309      }
5310  
5311      /**
5312       * Check if the directory must be set, depending on backup/backup_auto_storage.
5313       *
5314       * Note: backup/backup_auto_storage must be specified BEFORE this setting otherwise
5315       * there will be conflicts if this validation happens before the other one.
5316       *
5317       * @param string $data Form data.
5318       * @return string Empty when no errors.
5319       */
5320      public function write_setting($data) {
5321          $storage = (int) get_config('backup', 'backup_auto_storage');
5322          if ($storage !== 0) {
5323              if (empty($data) || !file_exists($data) || !is_dir($data) || !is_writable($data) ) {
5324                  // The directory must exist and be writable.
5325                  return get_string('backuperrorinvaliddestination');
5326              }
5327          }
5328          return parent::write_setting($data);
5329      }
5330  }
5331  
5332  
5333  /**
5334   * Special debug setting
5335   *
5336   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5337   */
5338  class admin_setting_special_debug extends admin_setting_configselect {
5339      /**
5340       * Calls parent::__construct with specific arguments
5341       */
5342      public function __construct() {
5343          parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
5344      }
5345  
5346      /**
5347       * Load the available choices for the select box
5348       *
5349       * @return bool
5350       */
5351      public function load_choices() {
5352          if (is_array($this->choices)) {
5353              return true;
5354          }
5355          $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
5356              DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
5357              DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
5358              DEBUG_ALL       => get_string('debugall', 'admin'),
5359              DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
5360          return true;
5361      }
5362  }
5363  
5364  
5365  /**
5366   * Special admin control
5367   *
5368   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5369   */
5370  class admin_setting_special_calendar_weekend extends admin_setting {
5371      /**
5372       * Calls parent::__construct with specific arguments
5373       */
5374      public function __construct() {
5375          $name = 'calendar_weekend';
5376          $visiblename = get_string('calendar_weekend', 'admin');
5377          $description = get_string('helpweekenddays', 'admin');
5378          $default = array ('0', '6'); // Saturdays and Sundays
5379          parent::__construct($name, $visiblename, $description, $default);
5380      }
5381  
5382      /**
5383       * Gets the current settings as an array
5384       *
5385       * @return mixed Null if none, else array of settings
5386       */
5387      public function get_setting() {
5388          $result = $this->config_read($this->name);
5389          if (is_null($result)) {
5390              return NULL;
5391          }
5392          if ($result === '') {
5393              return array();
5394          }
5395          $settings = array();
5396          for ($i=0; $i<7; $i++) {
5397              if ($result & (1 << $i)) {
5398                  $settings[] = $i;
5399              }
5400          }
5401          return $settings;
5402      }
5403  
5404      /**
5405       * Save the new settings
5406       *
5407       * @param array $data Array of new settings
5408       * @return bool
5409       */
5410      public function write_setting($data) {
5411          if (!is_array($data)) {
5412              return '';
5413          }
5414          unset($data['xxxxx']);
5415          $result = 0;
5416          foreach($data as $index) {
5417              $result |= 1 << $index;
5418          }
5419          return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
5420      }
5421  
5422      /**
5423       * Return XHTML to display the control
5424       *
5425       * @param array $data array of selected days
5426       * @param string $query
5427       * @return string XHTML for display (field + wrapping div(s)
5428       */
5429      public function output_html($data, $query='') {
5430          global $OUTPUT;
5431  
5432          // The order matters very much because of the implied numeric keys.
5433          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5434          $context = (object) [
5435              'name' => $this->get_full_name(),
5436              'id' => $this->get_id(),
5437              'days' => array_map(function($index) use ($days, $data) {
5438                  return [
5439                      'index' => $index,
5440                      'label' => get_string($days[$index], 'calendar'),
5441                      'checked' => in_array($index, $data)
5442                  ];
5443              }, array_keys($days))
5444          ];
5445  
5446          $element = $OUTPUT->render_from_template('core_admin/setting_special_calendar_weekend', $context);
5447  
5448          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
5449  
5450      }
5451  }
5452  
5453  
5454  /**
5455   * Admin setting that allows a user to pick a behaviour.
5456   *
5457   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5458   */
5459  class admin_setting_question_behaviour extends admin_setting_configselect {
5460      /**
5461       * @param string $name name of config variable
5462       * @param string $visiblename display name
5463       * @param string $description description
5464       * @param string $default default.
5465       */
5466      public function __construct($name, $visiblename, $description, $default) {
5467          parent::__construct($name, $visiblename, $description, $default, null);
5468      }
5469  
5470      /**
5471       * Load list of behaviours as choices
5472       * @return bool true => success, false => error.
5473       */
5474      public function load_choices() {
5475          global $CFG;
5476          require_once($CFG->dirroot . '/question/engine/lib.php');
5477          $this->choices = question_engine::get_behaviour_options('');
5478          return true;
5479      }
5480  }
5481  
5482  
5483  /**
5484   * Admin setting that allows a user to pick appropriate roles for something.
5485   *
5486   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5487   */
5488  class admin_setting_pickroles extends admin_setting_configmulticheckbox {
5489      /** @var array Array of capabilities which identify roles */
5490      private $types;
5491  
5492      /**
5493       * @param string $name Name of config variable
5494       * @param string $visiblename Display name
5495       * @param string $description Description
5496       * @param array $types Array of archetypes which identify
5497       *              roles that will be enabled by default.
5498       */
5499      public function __construct($name, $visiblename, $description, $types) {
5500          parent::__construct($name, $visiblename, $description, NULL, NULL);
5501          $this->types = $types;
5502      }
5503  
5504      /**
5505       * Load roles as choices
5506       *
5507       * @return bool true=>success, false=>error
5508       */
5509      public function load_choices() {
5510          global $CFG, $DB;
5511          if (during_initial_install()) {
5512              return false;
5513          }
5514          if (is_array($this->choices)) {
5515              return true;
5516          }
5517          if ($roles = get_all_roles()) {
5518              $this->choices = role_fix_names($roles, null, ROLENAME_ORIGINAL, true);
5519              return true;
5520          } else {
5521              return false;
5522          }
5523      }
5524  
5525      /**
5526       * Return the default setting for this control
5527       *
5528       * @return array Array of default settings
5529       */
5530      public function get_defaultsetting() {
5531          global $CFG;
5532  
5533          if (during_initial_install()) {
5534              return null;
5535          }
5536          $result = array();
5537          foreach($this->types as $archetype) {
5538              if ($caproles = get_archetype_roles($archetype)) {
5539                  foreach ($caproles as $caprole) {
5540                      $result[$caprole->id] = 1;
5541                  }
5542              }
5543          }
5544          return $result;
5545      }
5546  }
5547  
5548  
5549  /**
5550   * Admin setting that is a list of installed filter plugins.
5551   *
5552   * @copyright 2015 The Open University
5553   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5554   */
5555  class admin_setting_pickfilters extends admin_setting_configmulticheckbox {
5556  
5557      /**
5558       * Constructor
5559       *
5560       * @param string $name unique ascii name, either 'mysetting' for settings
5561       *      that in config, or 'myplugin/mysetting' for ones in config_plugins.
5562       * @param string $visiblename localised name
5563       * @param string $description localised long description
5564       * @param array $default the default. E.g. array('urltolink' => 1, 'emoticons' => 1)
5565       */
5566      public function __construct($name, $visiblename, $description, $default) {
5567          if (empty($default)) {
5568              $default = array();
5569          }
5570          $this->load_choices();
5571          foreach ($default as $plugin) {
5572              if (!isset($this->choices[$plugin])) {
5573                  unset($default[$plugin]);
5574              }
5575          }
5576          parent::__construct($name, $visiblename, $description, $default, null);
5577      }
5578  
5579      public function load_choices() {
5580          if (is_array($this->choices)) {
5581              return true;
5582          }
5583          $this->choices = array();
5584  
5585          foreach (core_component::get_plugin_list('filter') as $plugin => $unused) {
5586              $this->choices[$plugin] = filter_get_name($plugin);
5587          }
5588          return true;
5589      }
5590  }
5591  
5592  
5593  /**
5594   * Text field with an advanced checkbox, that controls a additional $name.'_adv' setting.
5595   *
5596   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5597   */
5598  class admin_setting_configtext_with_advanced extends admin_setting_configtext {
5599      /**
5600       * Constructor
5601       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5602       * @param string $visiblename localised
5603       * @param string $description long localised info
5604       * @param array $defaultsetting ('value'=>string, '__construct'=>bool)
5605       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
5606       * @param int $size default field size
5607       */
5608      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
5609          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $paramtype, $size);
5610          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5611      }
5612  }
5613  
5614  
5615  /**
5616   * Checkbox with an advanced checkbox that controls an additional $name.'_adv' config setting.
5617   *
5618   * @copyright 2009 Petr Skoda (http://skodak.org)
5619   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5620   */
5621  class admin_setting_configcheckbox_with_advanced extends admin_setting_configcheckbox {
5622  
5623      /**
5624       * Constructor
5625       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5626       * @param string $visiblename localised
5627       * @param string $description long localised info
5628       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
5629       * @param string $yes value used when checked
5630       * @param string $no value used when not checked
5631       */
5632      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5633          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5634          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5635      }
5636  
5637  }
5638  
5639  
5640  /**
5641   * Checkbox with an advanced checkbox that controls an additional $name.'_locked' config setting.
5642   *
5643   * This is nearly a copy/paste of admin_setting_configcheckbox_with_adv
5644   *
5645   * @copyright 2010 Sam Hemelryk
5646   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5647   */
5648  class admin_setting_configcheckbox_with_lock extends admin_setting_configcheckbox {
5649      /**
5650       * Constructor
5651       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5652       * @param string $visiblename localised
5653       * @param string $description long localised info
5654       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5655       * @param string $yes value used when checked
5656       * @param string $no value used when not checked
5657       */
5658      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5659          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5660          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5661      }
5662  
5663  }
5664  
5665  /**
5666   * Autocomplete as you type form element.
5667   *
5668   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5669   */
5670  class admin_setting_configselect_autocomplete extends admin_setting_configselect {
5671      /** @var boolean $tags Should we allow typing new entries to the field? */
5672      protected $tags = false;
5673      /** @var string $ajax Name of an AMD module to send/process ajax requests. */
5674      protected $ajax = '';
5675      /** @var string $placeholder Placeholder text for an empty list. */
5676      protected $placeholder = '';
5677      /** @var bool $casesensitive Whether the search has to be case-sensitive. */
5678      protected $casesensitive = false;
5679      /** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
5680      protected $showsuggestions = true;
5681      /** @var string $noselectionstring String that is shown when there are no selections. */
5682      protected $noselectionstring = '';
5683  
5684      /**
5685       * Returns XHTML select field and wrapping div(s)
5686       *
5687       * @see output_select_html()
5688       *
5689       * @param string $data the option to show as selected
5690       * @param string $query
5691       * @return string XHTML field and wrapping div
5692       */
5693      public function output_html($data, $query='') {
5694          global $PAGE;
5695  
5696          $html = parent::output_html($data, $query);
5697  
5698          if ($html === '') {
5699              return $html;
5700          }
5701  
5702          $this->placeholder = get_string('search');
5703  
5704          $params = array('#' . $this->get_id(), $this->tags, $this->ajax,
5705              $this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring);
5706  
5707          // Load autocomplete wrapper for select2 library.
5708          $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params);
5709  
5710          return $html;
5711      }
5712  }
5713  
5714  /**
5715   * Dropdown menu with an advanced checkbox, that controls a additional $name.'_adv' setting.
5716   *
5717   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5718   */
5719  class admin_setting_configselect_with_advanced extends admin_setting_configselect {
5720      /**
5721       * Calls parent::__construct with specific arguments
5722       */
5723      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5724          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5725          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5726      }
5727  
5728  }
5729  
5730  /**
5731   * Select with an advanced checkbox that controls an additional $name.'_locked' config setting.
5732   *
5733   * @copyright 2017 Marina Glancy
5734   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5735   */
5736  class admin_setting_configselect_with_lock extends admin_setting_configselect {
5737      /**
5738       * Constructor
5739       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
5740       *     or 'myplugin/mysetting' for ones in config_plugins.
5741       * @param string $visiblename localised
5742       * @param string $description long localised info
5743       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5744       * @param array $choices array of $value=>$label for each selection
5745       */
5746      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5747          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5748          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5749      }
5750  }
5751  
5752  
5753  /**
5754   * Graded roles in gradebook
5755   *
5756   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5757   */
5758  class admin_setting_special_gradebookroles extends admin_setting_pickroles {
5759      /**
5760       * Calls parent::__construct with specific arguments
5761       */
5762      public function __construct() {
5763          parent::__construct('gradebookroles', get_string('gradebookroles', 'admin'),
5764              get_string('configgradebookroles', 'admin'),
5765              array('student'));
5766      }
5767  }
5768  
5769  
5770  /**
5771   *
5772   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5773   */
5774  class admin_setting_regradingcheckbox extends admin_setting_configcheckbox {
5775      /**
5776       * Saves the new settings passed in $data
5777       *
5778       * @param string $data
5779       * @return mixed string or Array
5780       */
5781      public function write_setting($data) {
5782          global $CFG, $DB;
5783  
5784          $oldvalue  = $this->config_read($this->name);
5785          $return    = parent::write_setting($data);
5786          $newvalue  = $this->config_read($this->name);
5787  
5788          if ($oldvalue !== $newvalue) {
5789          // force full regrading
5790              $DB->set_field('grade_items', 'needsupdate', 1, array('needsupdate'=>0));
5791          }
5792  
5793          return $return;
5794      }
5795  }
5796  
5797  
5798  /**
5799   * Which roles to show on course description page
5800   *
5801   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5802   */
5803  class admin_setting_special_coursecontact extends admin_setting_pickroles {
5804      /**
5805       * Calls parent::__construct with specific arguments
5806       */
5807      public function __construct() {
5808          parent::__construct('coursecontact', get_string('coursecontact', 'admin'),
5809              get_string('coursecontact_desc', 'admin'),
5810              array('editingteacher'));
5811          $this->set_updatedcallback(function (){
5812              cache::make('core', 'coursecontacts')->purge();
5813          });
5814      }
5815  }
5816  
5817  
5818  /**
5819   *
5820   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5821   */
5822  class admin_setting_special_gradelimiting extends admin_setting_configcheckbox {
5823      /**
5824       * Calls parent::__construct with specific arguments
5825       */
5826      public function __construct() {
5827          parent::__construct('unlimitedgrades', get_string('unlimitedgrades', 'grades'),
5828              get_string('unlimitedgrades_help', 'grades'), '0', '1', '0');
5829      }
5830  
5831      /**
5832       * Old syntax of class constructor. Deprecated in PHP7.
5833       *
5834       * @deprecated since Moodle 3.1
5835       */
5836      public function admin_setting_special_gradelimiting() {
5837          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
5838          self::__construct();
5839      }
5840  
5841      /**
5842       * Force site regrading
5843       */
5844      function regrade_all() {
5845          global $CFG;
5846          require_once("$CFG->libdir/gradelib.php");
5847          grade_force_site_regrading();
5848      }
5849  
5850      /**
5851       * Saves the new settings
5852       *
5853       * @param mixed $data
5854       * @return string empty string or error message
5855       */
5856      function write_setting($data) {
5857          $previous = $this->get_setting();
5858  
5859          if ($previous === null) {
5860              if ($data) {
5861                  $this->regrade_all();
5862              }
5863          } else {
5864              if ($data != $previous) {
5865                  $this->regrade_all();
5866              }
5867          }
5868          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
5869      }
5870  
5871  }
5872  
5873  /**
5874   * Special setting for $CFG->grade_minmaxtouse.
5875   *
5876   * @package    core
5877   * @copyright  2015 Frédéric Massart - FMCorz.net
5878   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5879   */
5880  class admin_setting_special_grademinmaxtouse extends admin_setting_configselect {
5881  
5882      /**
5883       * Constructor.
5884       */
5885      public function __construct() {
5886          parent::__construct('grade_minmaxtouse', new lang_string('minmaxtouse', 'grades'),
5887              new lang_string('minmaxtouse_desc', 'grades'), GRADE_MIN_MAX_FROM_GRADE_ITEM,
5888              array(
5889                  GRADE_MIN_MAX_FROM_GRADE_ITEM => get_string('gradeitemminmax', 'grades'),
5890                  GRADE_MIN_MAX_FROM_GRADE_GRADE => get_string('gradegrademinmax', 'grades')
5891              )
5892          );
5893      }
5894  
5895      /**
5896       * Saves the new setting.
5897       *
5898       * @param mixed $data
5899       * @return string empty string or error message
5900       */
5901      function write_setting($data) {
5902          global $CFG;
5903  
5904          $previous = $this->get_setting();
5905          $result = parent::write_setting($data);
5906  
5907          // If saved and the value has changed.
5908          if (empty($result) && $previous != $data) {
5909              require_once($CFG->libdir . '/gradelib.php');
5910              grade_force_site_regrading();
5911          }
5912  
5913          return $result;
5914      }
5915  
5916  }
5917  
5918  
5919  /**
5920   * Primary grade export plugin - has state tracking.
5921   *
5922   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5923   */
5924  class admin_setting_special_gradeexport extends admin_setting_configmulticheckbox {
5925      /**
5926       * Calls parent::__construct with specific arguments
5927       */
5928      public function __construct() {
5929          parent::__construct('gradeexport', get_string('gradeexport', 'admin'),
5930              get_string('configgradeexport', 'admin'), array(), NULL);
5931      }
5932  
5933      /**
5934       * Load the available choices for the multicheckbox
5935       *
5936       * @return bool always returns true
5937       */
5938      public function load_choices() {
5939          if (is_array($this->choices)) {
5940              return true;
5941          }
5942          $this->choices = array();
5943  
5944          if ($plugins = core_component::get_plugin_list('gradeexport')) {
5945              foreach($plugins as $plugin => $unused) {
5946                  $this->choices[$plugin] = get_string('pluginname', 'gradeexport_'.$plugin);
5947              }
5948          }
5949          return true;
5950      }
5951  }
5952  
5953  
5954  /**
5955   * A setting for setting the default grade point value. Must be an integer between 1 and $CFG->gradepointmax.
5956   *
5957   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5958   */
5959  class admin_setting_special_gradepointdefault extends admin_setting_configtext {
5960      /**
5961       * Config gradepointmax constructor
5962       *
5963       * @param string $name Overidden by "gradepointmax"
5964       * @param string $visiblename Overridden by "gradepointmax" language string.
5965       * @param string $description Overridden by "gradepointmax_help" language string.
5966       * @param string $defaultsetting Not used, overridden by 100.
5967       * @param mixed $paramtype Overridden by PARAM_INT.
5968       * @param int $size Overridden by 5.
5969       */
5970      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
5971          $name = 'gradepointdefault';
5972          $visiblename = get_string('gradepointdefault', 'grades');
5973          $description = get_string('gradepointdefault_help', 'grades');
5974          $defaultsetting = 100;
5975          $paramtype = PARAM_INT;
5976          $size = 5;
5977          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
5978      }
5979  
5980      /**
5981       * Validate data before storage
5982       * @param string $data The submitted data
5983       * @return bool|string true if ok, string if error found
5984       */
5985      public function validate($data) {
5986          global $CFG;
5987          if (((string)(int)$data === (string)$data && $data > 0 && $data <= $CFG->gradepointmax)) {
5988              return true;
5989          } else {
5990              return get_string('gradepointdefault_validateerror', 'grades');
5991          }
5992      }
5993  }
5994  
5995  
5996  /**
5997   * A setting for setting the maximum grade value. Must be an integer between 1 and 10000.
5998   *
5999   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6000   */
6001  class admin_setting_special_gradepointmax extends admin_setting_configtext {
6002  
6003      /**
6004       * Config gradepointmax constructor
6005       *
6006       * @param string $name Overidden by "gradepointmax"
6007       * @param string $visiblename Overridden by "gradepointmax" language string.
6008       * @param string $description Overridden by "gradepointmax_help" language string.
6009       * @param string $defaultsetting Not used, overridden by 100.
6010       * @param mixed $paramtype Overridden by PARAM_INT.
6011       * @param int $size Overridden by 5.
6012       */
6013      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
6014          $name = 'gradepointmax';
6015          $visiblename = get_string('gradepointmax', 'grades');
6016          $description = get_string('gradepointmax_help', 'grades');
6017          $defaultsetting = 100;
6018          $paramtype = PARAM_INT;
6019          $size = 5;
6020          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
6021      }
6022  
6023      /**
6024       * Save the selected setting
6025       *
6026       * @param string $data The selected site
6027       * @return string empty string or error message
6028       */
6029      public function write_setting($data) {
6030          if ($data === '') {
6031              $data = (int)$this->defaultsetting;
6032          } else {
6033              $data = $data;
6034          }
6035          return parent::write_setting($data);
6036      }
6037  
6038      /**
6039       * Validate data before storage
6040       * @param string $data The submitted data
6041       * @return bool|string true if ok, string if error found
6042       */
6043      public function validate($data) {
6044          if (((string)(int)$data === (string)$data && $data > 0 && $data <= 10000)) {
6045              return true;
6046          } else {
6047              return get_string('gradepointmax_validateerror', 'grades');
6048          }
6049      }
6050  
6051      /**
6052       * Return an XHTML string for the setting
6053       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6054       * @param string $query search query to be highlighted
6055       * @return string XHTML to display control
6056       */
6057      public function output_html($data, $query = '') {
6058          global $OUTPUT;
6059  
6060          $default = $this->get_defaultsetting();
6061          $context = (object) [
6062              'size' => $this->size,
6063              'id' => $this->get_id(),
6064              'name' => $this->get_full_name(),
6065              'value' => $data,
6066              'attributes' => [
6067                  'maxlength' => 5
6068              ],
6069              'forceltr' => $this->get_force_ltr()
6070          ];
6071          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
6072  
6073          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
6074      }
6075  }
6076  
6077  
6078  /**
6079   * Grade category settings
6080   *
6081   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6082   */
6083  class admin_setting_gradecat_combo extends admin_setting {
6084      /** @var array Array of choices */
6085      public $choices;
6086  
6087      /**
6088       * Sets choices and calls parent::__construct with passed arguments
6089       * @param string $name
6090       * @param string $visiblename
6091       * @param string $description
6092       * @param mixed $defaultsetting string or array depending on implementation
6093       * @param array $choices An array of choices for the control
6094       */
6095      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
6096          $this->choices = $choices;
6097          parent::__construct($name, $visiblename, $description, $defaultsetting);
6098      }
6099  
6100      /**
6101       * Return the current setting(s) array
6102       *
6103       * @return array Array of value=>xx, forced=>xx, adv=>xx
6104       */
6105      public function get_setting() {
6106          global $CFG;
6107  
6108          $value = $this->config_read($this->name);
6109          $flag  = $this->config_read($this->name.'_flag');
6110  
6111          if (is_null($value) or is_null($flag)) {
6112              return NULL;
6113          }
6114  
6115          $flag   = (int)$flag;
6116          $forced = (boolean)(1 & $flag); // first bit
6117          $adv    = (boolean)(2 & $flag); // second bit
6118  
6119          return array('value' => $value, 'forced' => $forced, 'adv' => $adv);
6120      }
6121  
6122      /**
6123       * Save the new settings passed in $data
6124       *
6125       * @todo Add vartype handling to ensure $data is array
6126       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6127       * @return string empty or error message
6128       */
6129      public function write_setting($data) {
6130          global $CFG;
6131  
6132          $value  = $data['value'];
6133          $forced = empty($data['forced']) ? 0 : 1;
6134          $adv    = empty($data['adv'])    ? 0 : 2;
6135          $flag   = ($forced | $adv); //bitwise or
6136  
6137          if (!in_array($value, array_keys($this->choices))) {
6138              return 'Error setting ';
6139          }
6140  
6141          $oldvalue  = $this->config_read($this->name);
6142          $oldflag   = (int)$this->config_read($this->name.'_flag');
6143          $oldforced = (1 & $oldflag); // first bit
6144  
6145          $result1 = $this->config_write($this->name, $value);
6146          $result2 = $this->config_write($this->name.'_flag', $flag);
6147  
6148          // force regrade if needed
6149          if ($oldforced != $forced or ($forced and $value != $oldvalue)) {
6150              require_once($CFG->libdir.'/gradelib.php');
6151              grade_category::updated_forced_settings();
6152          }
6153  
6154          if ($result1 and $result2) {
6155              return '';
6156          } else {
6157              return get_string('errorsetting', 'admin');
6158          }
6159      }
6160  
6161      /**
6162       * Return XHTML to display the field and wrapping div
6163       *
6164       * @todo Add vartype handling to ensure $data is array
6165       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6166       * @param string $query
6167       * @return string XHTML to display control
6168       */
6169      public function output_html($data, $query='') {
6170          global $OUTPUT;
6171  
6172          $value  = $data['value'];
6173  
6174          $default = $this->get_defaultsetting();
6175          if (!is_null($default)) {
6176              $defaultinfo = array();
6177              if (isset($this->choices[$default['value']])) {
6178                  $defaultinfo[] = $this->choices[$default['value']];
6179              }
6180              if (!empty($default['forced'])) {
6181                  $defaultinfo[] = get_string('force');
6182              }
6183              if (!empty($default['adv'])) {
6184                  $defaultinfo[] = get_string('advanced');
6185              }
6186              $defaultinfo = implode(', ', $defaultinfo);
6187  
6188          } else {
6189              $defaultinfo = NULL;
6190          }
6191  
6192          $options = $this->choices;
6193          $context = (object) [
6194              'id' => $this->get_id(),
6195              'name' => $this->get_full_name(),
6196              'forced' => !empty($data['forced']),
6197              'advanced' => !empty($data['adv']),
6198              'options' => array_map(function($option) use ($options, $value) {
6199                  return [
6200                      'value' => $option,
6201                      'name' => $options[$option],
6202                      'selected' => $option == $value
6203                  ];
6204              }, array_keys($options)),
6205          ];
6206  
6207          $element = $OUTPUT->render_from_template('core_admin/setting_gradecat_combo', $context);
6208  
6209          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
6210      }
6211  }
6212  
6213  
6214  /**
6215   * Selection of grade report in user profiles
6216   *
6217   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6218   */
6219  class admin_setting_grade_profilereport extends admin_setting_configselect {
6220      /**
6221       * Calls parent::__construct with specific arguments
6222       */
6223      public function __construct() {
6224          parent::__construct('grade_profilereport', get_string('profilereport', 'grades'), get_string('profilereport_help', 'grades'), 'user', null);
6225      }
6226  
6227      /**
6228       * Loads an array of choices for the configselect control
6229       *
6230       * @return bool always return true
6231       */
6232      public function load_choices() {
6233          if (is_array($this->choices)) {
6234              return true;
6235          }
6236          $this->choices = array();
6237  
6238          global $CFG;
6239          require_once($CFG->libdir.'/gradelib.php');
6240  
6241          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6242              if (file_exists($plugindir.'/lib.php')) {
6243                  require_once($plugindir.'/lib.php');
6244                  $functionname = 'grade_report_'.$plugin.'_profilereport';
6245                  if (function_exists($functionname)) {
6246                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_'.$plugin);
6247                  }
6248              }
6249          }
6250          return true;
6251      }
6252  }
6253  
6254  /**
6255   * Provides a selection of grade reports to be used for "grades".
6256   *
6257   * @copyright 2015 Adrian Greeve <adrian@moodle.com>
6258   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6259   */
6260  class admin_setting_my_grades_report extends admin_setting_configselect {
6261  
6262      /**
6263       * Calls parent::__construct with specific arguments.
6264       */
6265      public function __construct() {
6266          parent::__construct('grade_mygrades_report', new lang_string('mygrades', 'grades'),
6267                  new lang_string('mygrades_desc', 'grades'), 'overview', null);
6268      }
6269  
6270      /**
6271       * Loads an array of choices for the configselect control.
6272       *
6273       * @return bool always returns true.
6274       */
6275      public function load_choices() {
6276          global $CFG; // Remove this line and behold the horror of behat test failures!
6277          $this->choices = array();
6278          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6279              if (file_exists($plugindir . '/lib.php')) {
6280                  require_once($plugindir . '/lib.php');
6281                  // Check to see if the class exists. Check the correct plugin convention first.
6282                  if (class_exists('gradereport_' . $plugin)) {
6283                      $classname = 'gradereport_' . $plugin;
6284                  } else if (class_exists('grade_report_' . $plugin)) {
6285                      // We are using the old plugin naming convention.
6286                      $classname = 'grade_report_' . $plugin;
6287                  } else {
6288                      continue;
6289                  }
6290                  if ($classname::supports_mygrades()) {
6291                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_' . $plugin);
6292                  }
6293              }
6294          }
6295          // Add an option to specify an external url.
6296          $this->choices['external'] = get_string('externalurl', 'grades');
6297          return true;
6298      }
6299  }
6300  
6301  /**
6302   * Special class for register auth selection
6303   *
6304   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6305   */
6306  class admin_setting_special_registerauth extends admin_setting_configselect {
6307      /**
6308       * Calls parent::__construct with specific arguments
6309       */
6310      public function __construct() {
6311          parent::__construct('registerauth', get_string('selfregistration', 'auth'), get_string('selfregistration_help', 'auth'), '', null);
6312      }
6313  
6314      /**
6315       * Returns the default option
6316       *
6317       * @return string empty or default option
6318       */
6319      public function get_defaultsetting() {
6320          $this->load_choices();
6321          $defaultsetting = parent::get_defaultsetting();
6322          if (array_key_exists($defaultsetting, $this->choices)) {
6323              return $defaultsetting;
6324          } else {
6325              return '';
6326          }
6327      }
6328  
6329      /**
6330       * Loads the possible choices for the array
6331       *
6332       * @return bool always returns true
6333       */
6334      public function load_choices() {
6335          global $CFG;
6336  
6337          if (is_array($this->choices)) {
6338              return true;
6339          }
6340          $this->choices = array();
6341          $this->choices[''] = get_string('disable');
6342  
6343          $authsenabled = get_enabled_auth_plugins();
6344  
6345          foreach ($authsenabled as $auth) {
6346              $authplugin = get_auth_plugin($auth);
6347              if (!$authplugin->can_signup()) {
6348                  continue;
6349              }
6350              // Get the auth title (from core or own auth lang files)
6351              $authtitle = $authplugin->get_title();
6352              $this->choices[$auth] = $authtitle;
6353          }
6354          return true;
6355      }
6356  }
6357  
6358  
6359  /**
6360   * General plugins manager
6361   */
6362  class admin_page_pluginsoverview extends admin_externalpage {
6363  
6364      /**
6365       * Sets basic information about the external page
6366       */
6367      public function __construct() {
6368          global $CFG;
6369          parent::__construct('pluginsoverview', get_string('pluginsoverview', 'core_admin'),
6370              "$CFG->wwwroot/$CFG->admin/plugins.php");
6371      }
6372  }
6373  
6374  /**
6375   * Module manage page
6376   *
6377   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6378   */
6379  class admin_page_managemods extends admin_externalpage {
6380      /**
6381       * Calls parent::__construct with specific arguments
6382       */
6383      public function __construct() {
6384          global $CFG;
6385          parent::__construct('managemodules', get_string('modsettings', 'admin'), "$CFG->wwwroot/$CFG->admin/modules.php");
6386      }
6387  
6388      /**
6389       * Try to find the specified module
6390       *
6391       * @param string $query The module to search for
6392       * @return array
6393       */
6394      public function search($query) {
6395          global $CFG, $DB;
6396          if ($result = parent::search($query)) {
6397              return $result;
6398          }
6399  
6400          $found = false;
6401          if ($modules = $DB->get_records('modules')) {
6402              foreach ($modules as $module) {
6403                  if (!file_exists("$CFG->dirroot/mod/$module->name/lib.php")) {
6404                      continue;
6405                  }
6406                  if (strpos($module->name, $query) !== false) {
6407                      $found = true;
6408                      break;
6409                  }
6410                  $strmodulename = get_string('modulename', $module->name);
6411                  if (strpos(core_text::strtolower($strmodulename), $query) !== false) {
6412                      $found = true;
6413                      break;
6414                  }
6415              }
6416          }
6417          if ($found) {
6418              $result = new stdClass();
6419              $result->page     = $this;
6420              $result->settings = array();
6421              return array($this->name => $result);
6422          } else {
6423              return array();
6424          }
6425      }
6426  }
6427  
6428  
6429  /**
6430   * Special class for enrol plugins management.
6431   *
6432   * @copyright 2010 Petr Skoda {@link http://skodak.org}
6433   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6434   */
6435  class admin_setting_manageenrols extends admin_setting {
6436      /**
6437       * Calls parent::__construct with specific arguments
6438       */
6439      public function __construct() {
6440          $this->nosave = true;
6441          parent::__construct('enrolsui', get_string('manageenrols', 'enrol'), '', '');
6442      }
6443  
6444      /**
6445       * Always returns true, does nothing
6446       *
6447       * @return true
6448       */
6449      public function get_setting() {
6450          return true;
6451      }
6452  
6453      /**
6454       * Always returns true, does nothing
6455       *
6456       * @return true
6457       */
6458      public function get_defaultsetting() {
6459          return true;
6460      }
6461  
6462      /**
6463       * Always returns '', does not write anything
6464       *
6465       * @return string Always returns ''
6466       */
6467      public function write_setting($data) {
6468      // do not write any setting
6469          return '';
6470      }
6471  
6472      /**
6473       * Checks if $query is one of the available enrol plugins
6474       *
6475       * @param string $query The string to search for
6476       * @return bool Returns true if found, false if not
6477       */
6478      public function is_related($query) {
6479          if (parent::is_related($query)) {
6480              return true;
6481          }
6482  
6483          $query = core_text::strtolower($query);
6484          $enrols = enrol_get_plugins(false);
6485          foreach ($enrols as $name=>$enrol) {
6486              $localised = get_string('pluginname', 'enrol_'.$name);
6487              if (strpos(core_text::strtolower($name), $query) !== false) {
6488                  return true;
6489              }
6490              if (strpos(core_text::strtolower($localised), $query) !== false) {
6491                  return true;
6492              }
6493          }
6494          return false;
6495      }
6496  
6497      /**
6498       * Builds the XHTML to display the control
6499       *
6500       * @param string $data Unused
6501       * @param string $query
6502       * @return string
6503       */
6504      public function output_html($data, $query='') {
6505          global $CFG, $OUTPUT, $DB, $PAGE;
6506  
6507          // Display strings.
6508          $strup        = get_string('up');
6509          $strdown      = get_string('down');
6510          $strsettings  = get_string('settings');
6511          $strenable    = get_string('enable');
6512          $strdisable   = get_string('disable');
6513          $struninstall = get_string('uninstallplugin', 'core_admin');
6514          $strusage     = get_string('enrolusage', 'enrol');
6515          $strversion   = get_string('version');
6516          $strtest      = get_string('testsettings', 'core_enrol');
6517  
6518          $pluginmanager = core_plugin_manager::instance();
6519  
6520          $enrols_available = enrol_get_plugins(false);
6521          $active_enrols    = enrol_get_plugins(true);
6522  
6523          $allenrols = array();
6524          foreach ($active_enrols as $key=>$enrol) {
6525              $allenrols[$key] = true;
6526          }
6527          foreach ($enrols_available as $key=>$enrol) {
6528              $allenrols[$key] = true;
6529          }
6530          // Now find all borked plugins and at least allow then to uninstall.
6531          $condidates = $DB->get_fieldset_sql("SELECT DISTINCT enrol FROM {enrol}");
6532          foreach ($condidates as $candidate) {
6533              if (empty($allenrols[$candidate])) {
6534                  $allenrols[$candidate] = true;
6535              }
6536          }
6537  
6538          $return = $OUTPUT->heading(get_string('actenrolshhdr', 'enrol'), 3, 'main', true);
6539          $return .= $OUTPUT->box_start('generalbox enrolsui');
6540  
6541          $table = new html_table();
6542          $table->head  = array(get_string('name'), $strusage, $strversion, $strenable, $strup.'/'.$strdown, $strsettings, $strtest, $struninstall);
6543          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
6544          $table->id = 'courseenrolmentplugins';
6545          $table->attributes['class'] = 'admintable generaltable';
6546          $table->data  = array();
6547  
6548          // Iterate through enrol plugins and add to the display table.
6549          $updowncount = 1;
6550          $enrolcount = count($active_enrols);
6551          $url = new moodle_url('/admin/enrol.php', array('sesskey'=>sesskey()));
6552          $printed = array();
6553          foreach($allenrols as $enrol => $unused) {
6554              $plugininfo = $pluginmanager->get_plugin_info('enrol_'.$enrol);
6555              $version = get_config('enrol_'.$enrol, 'version');
6556              if ($version === false) {
6557                  $version = '';
6558              }
6559  
6560              if (get_string_manager()->string_exists('pluginname', 'enrol_'.$enrol)) {
6561                  $name = get_string('pluginname', 'enrol_'.$enrol);
6562              } else {
6563                  $name = $enrol;
6564              }
6565              // Usage.
6566              $ci = $DB->count_records('enrol', array('enrol'=>$enrol));
6567              $cp = $DB->count_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($enrol));
6568              $usage = "$ci / $cp";
6569  
6570              // Hide/show links.
6571              $class = '';
6572              if (isset($active_enrols[$enrol])) {
6573                  $aurl = new moodle_url($url, array('action'=>'disable', 'enrol'=>$enrol));
6574                  $hideshow = "<a href=\"$aurl\">";
6575                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
6576                  $enabled = true;
6577                  $displayname = $name;
6578              } else if (isset($enrols_available[$enrol])) {
6579                  $aurl = new moodle_url($url, array('action'=>'enable', 'enrol'=>$enrol));
6580                  $hideshow = "<a href=\"$aurl\">";
6581                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
6582                  $enabled = false;
6583                  $displayname = $name;
6584                  $class = 'dimmed_text';
6585              } else {
6586                  $hideshow = '';
6587                  $enabled = false;
6588                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
6589              }
6590              if ($PAGE->theme->resolve_image_location('icon', 'enrol_' . $name, false)) {
6591                  $icon = $OUTPUT->pix_icon('icon', '', 'enrol_' . $name, array('class' => 'icon pluginicon'));
6592              } else {
6593                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
6594              }
6595  
6596              // Up/down link (only if enrol is enabled).
6597              $updown = '';
6598              if ($enabled) {
6599                  if ($updowncount > 1) {
6600                      $aurl = new moodle_url($url, array('action'=>'up', 'enrol'=>$enrol));
6601                      $updown .= "<a href=\"$aurl\">";
6602                      $updown .= $OUTPUT->pix_icon('t/up', $strup) . '</a>&nbsp;';
6603                  } else {
6604                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6605                  }
6606                  if ($updowncount < $enrolcount) {
6607                      $aurl = new moodle_url($url, array('action'=>'down', 'enrol'=>$enrol));
6608                      $updown .= "<a href=\"$aurl\">";
6609                      $updown .= $OUTPUT->pix_icon('t/down', $strdown) . '</a>&nbsp;';
6610                  } else {
6611                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6612                  }
6613                  ++$updowncount;
6614              }
6615  
6616              // Add settings link.
6617              if (!$version) {
6618                  $settings = '';
6619              } else if ($surl = $plugininfo->get_settings_url()) {
6620                  $settings = html_writer::link($surl, $strsettings);
6621              } else {
6622                  $settings = '';
6623              }
6624  
6625              // Add uninstall info.
6626              $uninstall = '';
6627              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol, 'manage')) {
6628                  $uninstall = html_writer::link($uninstallurl, $struninstall);
6629              }
6630  
6631              $test = '';
6632              if (!empty($enrols_available[$enrol]) and method_exists($enrols_available[$enrol], 'test_settings')) {
6633                  $testsettingsurl = new moodle_url('/enrol/test_settings.php', array('enrol'=>$enrol, 'sesskey'=>sesskey()));
6634                  $test = html_writer::link($testsettingsurl, $strtest);
6635              }
6636  
6637              // Add a row to the table.
6638              $row = new html_table_row(array($icon.$displayname, $usage, $version, $hideshow, $updown, $settings, $test, $uninstall));
6639              if ($class) {
6640                  $row->attributes['class'] = $class;
6641              }
6642              $table->data[] = $row;
6643  
6644              $printed[$enrol] = true;
6645          }
6646  
6647          $return .= html_writer::table($table);
6648          $return .= get_string('configenrolplugins', 'enrol').'<br />'.get_string('tablenosave', 'admin');
6649          $return .= $OUTPUT->box_end();
6650          return highlight($query, $return);
6651      }
6652  }
6653  
6654  
6655  /**
6656   * Blocks manage page
6657   *
6658   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6659   */
6660  class admin_page_manageblocks extends admin_externalpage {
6661      /**
6662       * Calls parent::__construct with specific arguments
6663       */
6664      public function __construct() {
6665          global $CFG;
6666          parent::__construct('manageblocks', get_string('blocksettings', 'admin'), "$CFG->wwwroot/$CFG->admin/blocks.php");
6667      }
6668  
6669      /**
6670       * Search for a specific block
6671       *
6672       * @param string $query The string to search for
6673       * @return array
6674       */
6675      public function search($query) {
6676          global $CFG, $DB;
6677          if ($result = parent::search($query)) {
6678              return $result;
6679          }
6680  
6681          $found = false;
6682          if ($blocks = $DB->get_records('block')) {
6683              foreach ($blocks as $block) {
6684                  if (!file_exists("$CFG->dirroot/blocks/$block->name/")) {
6685                      continue;
6686                  }
6687                  if (strpos($block->name, $query) !== false) {
6688                      $found = true;
6689                      break;
6690                  }
6691                  $strblockname = get_string('pluginname', 'block_'.$block->name);
6692                  if (strpos(core_text::strtolower($strblockname), $query) !== false) {
6693                      $found = true;
6694                      break;
6695                  }
6696              }
6697          }
6698          if ($found) {
6699              $result = new stdClass();
6700              $result->page     = $this;
6701              $result->settings = array();
6702              return array($this->name => $result);
6703          } else {
6704              return array();
6705          }
6706      }
6707  }
6708  
6709  /**
6710   * Message outputs configuration
6711   *
6712   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6713   */
6714  class admin_page_managemessageoutputs extends admin_externalpage {
6715      /**
6716       * Calls parent::__construct with specific arguments
6717       */
6718      public function __construct() {
6719          global $CFG;
6720          parent::__construct('managemessageoutputs',
6721              get_string('defaultmessageoutputs', 'message'),
6722              new moodle_url('/admin/message.php')
6723          );
6724      }
6725  
6726      /**
6727       * Search for a specific message processor
6728       *
6729       * @param string $query The string to search for
6730       * @return array
6731       */
6732      public function search($query) {
6733          global $CFG, $DB;
6734          if ($result = parent::search($query)) {
6735              return $result;
6736          }
6737  
6738          $found = false;
6739          if ($processors = get_message_processors()) {
6740              foreach ($processors as $processor) {
6741                  if (!$processor->available) {
6742                      continue;
6743                  }
6744                  if (strpos($processor->name, $query) !== false) {
6745                      $found = true;
6746                      break;
6747                  }
6748                  $strprocessorname = get_string('pluginname', 'message_'.$processor->name);
6749                  if (strpos(core_text::strtolower($strprocessorname), $query) !== false) {
6750                      $found = true;
6751                      break;
6752                  }
6753              }
6754          }
6755          if ($found) {
6756              $result = new stdClass();
6757              $result->page     = $this;
6758              $result->settings = array();
6759              return array($this->name => $result);
6760          } else {
6761              return array();
6762          }
6763      }
6764  }
6765  
6766  /**
6767   * Manage question behaviours page
6768   *
6769   * @copyright  2011 The Open University
6770   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6771   */
6772  class admin_page_manageqbehaviours extends admin_externalpage {
6773      /**
6774       * Constructor
6775       */
6776      public function __construct() {
6777          global $CFG;
6778          parent::__construct('manageqbehaviours', get_string('manageqbehaviours', 'admin'),
6779                  new moodle_url('/admin/qbehaviours.php'));
6780      }
6781  
6782      /**
6783       * Search question behaviours for the specified string
6784       *
6785       * @param string $query The string to search for in question behaviours
6786       * @return array
6787       */
6788      public function search($query) {
6789          global $CFG;
6790          if ($result = parent::search($query)) {
6791              return $result;
6792          }
6793  
6794          $found = false;
6795          require_once($CFG->dirroot . '/question/engine/lib.php');
6796          foreach (core_component::get_plugin_list('qbehaviour') as $behaviour => $notused) {
6797              if (strpos(core_text::strtolower(question_engine::get_behaviour_name($behaviour)),
6798                      $query) !== false) {
6799                  $found = true;
6800                  break;
6801              }
6802          }
6803          if ($found) {
6804              $result = new stdClass();
6805              $result->page     = $this;
6806              $result->settings = array();
6807              return array($this->name => $result);
6808          } else {
6809              return array();
6810          }
6811      }
6812  }
6813  
6814  
6815  /**
6816   * Question type manage page
6817   *
6818   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6819   */
6820  class admin_page_manageqtypes extends admin_externalpage {
6821      /**
6822       * Calls parent::__construct with specific arguments
6823       */
6824      public function __construct() {
6825          global $CFG;
6826          parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'),
6827                  new moodle_url('/admin/qtypes.php'));
6828      }
6829  
6830      /**
6831       * Search question types for the specified string
6832       *
6833       * @param string $query The string to search for in question types
6834       * @return array
6835       */
6836      public function search($query) {
6837          global $CFG;
6838          if ($result = parent::search($query)) {
6839              return $result;
6840          }
6841  
6842          $found = false;
6843          require_once($CFG->dirroot . '/question/engine/bank.php');
6844          foreach (question_bank::get_all_qtypes() as $qtype) {
6845              if (strpos(core_text::strtolower($qtype->local_name()), $query) !== false) {
6846                  $found = true;
6847                  break;
6848              }
6849          }
6850          if ($found) {
6851              $result = new stdClass();
6852              $result->page     = $this;
6853              $result->settings = array();
6854              return array($this->name => $result);
6855          } else {
6856              return array();
6857          }
6858      }
6859  }
6860  
6861  
6862  class admin_page_manageportfolios extends admin_externalpage {
6863      /**
6864       * Calls parent::__construct with specific arguments
6865       */
6866      public function __construct() {
6867          global $CFG;
6868          parent::__construct('manageportfolios', get_string('manageportfolios', 'portfolio'),
6869                  "$CFG->wwwroot/$CFG->admin/portfolio.php");
6870      }
6871  
6872      /**
6873       * Searches page for the specified string.
6874       * @param string $query The string to search for
6875       * @return bool True if it is found on this page
6876       */
6877      public function search($query) {
6878          global $CFG;
6879          if ($result = parent::search($query)) {
6880              return $result;
6881          }
6882  
6883          $found = false;
6884          $portfolios = core_component::get_plugin_list('portfolio');
6885          foreach ($portfolios as $p => $dir) {
6886              if (strpos($p, $query) !== false) {
6887                  $found = true;
6888                  break;
6889              }
6890          }
6891          if (!$found) {
6892              foreach (portfolio_instances(false, false) as $instance) {
6893                  $title = $instance->get('name');
6894                  if (strpos(core_text::strtolower($title), $query) !== false) {
6895                      $found = true;
6896                      break;
6897                  }
6898              }
6899          }
6900  
6901          if ($found) {
6902              $result = new stdClass();
6903              $result->page     = $this;
6904              $result->settings = array();
6905              return array($this->name => $result);
6906          } else {
6907              return array();
6908          }
6909      }
6910  }
6911  
6912  
6913  class admin_page_managerepositories extends admin_externalpage {
6914      /**
6915       * Calls parent::__construct with specific arguments
6916       */
6917      public function __construct() {
6918          global $CFG;
6919          parent::__construct('managerepositories', get_string('manage',
6920                  'repository'), "$CFG->wwwroot/$CFG->admin/repository.php");
6921      }
6922  
6923      /**
6924       * Searches page for the specified string.
6925       * @param string $query The string to search for
6926       * @return bool True if it is found on this page
6927       */
6928      public function search($query) {
6929          global $CFG;
6930          if ($result = parent::search($query)) {
6931              return $result;
6932          }
6933  
6934          $found = false;
6935          $repositories= core_component::get_plugin_list('repository');
6936          foreach ($repositories as $p => $dir) {
6937              if (strpos($p, $query) !== false) {
6938                  $found = true;
6939                  break;
6940              }
6941          }
6942          if (!$found) {
6943              foreach (repository::get_types() as $instance) {
6944                  $title = $instance->get_typename();
6945                  if (strpos(core_text::strtolower($title), $query) !== false) {
6946                      $found = true;
6947                      break;
6948                  }
6949              }
6950          }
6951  
6952          if ($found) {
6953              $result = new stdClass();
6954              $result->page     = $this;
6955              $result->settings = array();
6956              return array($this->name => $result);
6957          } else {
6958              return array();
6959          }
6960      }
6961  }
6962  
6963  
6964  /**
6965   * Special class for authentication administration.
6966   *
6967   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6968   */
6969  class admin_setting_manageauths extends admin_setting {
6970      /**
6971       * Calls parent::__construct with specific arguments
6972       */
6973      public function __construct() {
6974          $this->nosave = true;
6975          parent::__construct('authsui', get_string('authsettings', 'admin'), '', '');
6976      }
6977  
6978      /**
6979       * Always returns true
6980       *
6981       * @return true
6982       */
6983      public function get_setting() {
6984          return true;
6985      }
6986  
6987      /**
6988       * Always returns true
6989       *
6990       * @return true
6991       */
6992      public function get_defaultsetting() {
6993          return true;
6994      }
6995  
6996      /**
6997       * Always returns '' and doesn't write anything
6998       *
6999       * @return string Always returns ''
7000       */
7001      public function write_setting($data) {
7002      // do not write any setting
7003          return '';
7004      }
7005  
7006      /**
7007       * Search to find if Query is related to auth plugin
7008       *
7009       * @param string $query The string to search for
7010       * @return bool true for related false for not
7011       */
7012      public function is_related($query) {
7013          if (parent::is_related($query)) {
7014              return true;
7015          }
7016  
7017          $authsavailable = core_component::get_plugin_list('auth');
7018          foreach ($authsavailable as $auth => $dir) {
7019              if (strpos($auth, $query) !== false) {
7020                  return true;
7021              }
7022              $authplugin = get_auth_plugin($auth);
7023              $authtitle = $authplugin->get_title();
7024              if (strpos(core_text::strtolower($authtitle), $query) !== false) {
7025                  return true;
7026              }
7027          }
7028          return false;
7029      }
7030  
7031      /**
7032       * Return XHTML to display control
7033       *
7034       * @param mixed $data Unused
7035       * @param string $query
7036       * @return string highlight
7037       */
7038      public function output_html($data, $query='') {
7039          global $CFG, $OUTPUT, $DB;
7040  
7041          // display strings
7042          $txt = get_strings(array('authenticationplugins', 'users', 'administration',
7043              'settings', 'edit', 'name', 'enable', 'disable',
7044              'up', 'down', 'none', 'users'));
7045          $txt->updown = "$txt->up/$txt->down";
7046          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7047          $txt->testsettings = get_string('testsettings', 'core_auth');
7048  
7049          $authsavailable = core_component::get_plugin_list('auth');
7050          get_enabled_auth_plugins(true); // fix the list of enabled auths
7051          if (empty($CFG->auth)) {
7052              $authsenabled = array();
7053          } else {
7054              $authsenabled = explode(',', $CFG->auth);
7055          }
7056  
7057          // construct the display array, with enabled auth plugins at the top, in order
7058          $displayauths = array();
7059          $registrationauths = array();
7060          $registrationauths[''] = $txt->disable;
7061          $authplugins = array();
7062          foreach ($authsenabled as $auth) {
7063              $authplugin = get_auth_plugin($auth);
7064              $authplugins[$auth] = $authplugin;
7065              /// Get the auth title (from core or own auth lang files)
7066              $authtitle = $authplugin->get_title();
7067              /// Apply titles
7068              $displayauths[$auth] = $authtitle;
7069              if ($authplugin->can_signup()) {
7070                  $registrationauths[$auth] = $authtitle;
7071              }
7072          }
7073  
7074          foreach ($authsavailable as $auth => $dir) {
7075              if (array_key_exists($auth, $displayauths)) {
7076                  continue; //already in the list
7077              }
7078              $authplugin = get_auth_plugin($auth);
7079              $authplugins[$auth] = $authplugin;
7080              /// Get the auth title (from core or own auth lang files)
7081              $authtitle = $authplugin->get_title();
7082              /// Apply titles
7083              $displayauths[$auth] = $authtitle;
7084              if ($authplugin->can_signup()) {
7085                  $registrationauths[$auth] = $authtitle;
7086              }
7087          }
7088  
7089          $return = $OUTPUT->heading(get_string('actauthhdr', 'auth'), 3, 'main');
7090          $return .= $OUTPUT->box_start('generalbox authsui');
7091  
7092          $table = new html_table();
7093          $table->head  = array($txt->name, $txt->users, $txt->enable, $txt->updown, $txt->settings, $txt->testsettings, $txt->uninstall);
7094          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7095          $table->data  = array();
7096          $table->attributes['class'] = 'admintable generaltable';
7097          $table->id = 'manageauthtable';
7098  
7099          //add always enabled plugins first
7100          $displayname = $displayauths['manual'];
7101          $settings = "<a href=\"settings.php?section=authsettingmanual\">{$txt->settings}</a>";
7102          $usercount = $DB->count_records('user', array('auth'=>'manual', 'deleted'=>0));
7103          $table->data[] = array($displayname, $usercount, '', '', $settings, '', '');
7104          $displayname = $displayauths['nologin'];
7105          $usercount = $DB->count_records('user', array('auth'=>'nologin', 'deleted'=>0));
7106          $table->data[] = array($displayname, $usercount, '', '', '', '', '');
7107  
7108  
7109          // iterate through auth plugins and add to the display table
7110          $updowncount = 1;
7111          $authcount = count($authsenabled);
7112          $url = "auth.php?sesskey=" . sesskey();
7113          foreach ($displayauths as $auth => $name) {
7114              if ($auth == 'manual' or $auth == 'nologin') {
7115                  continue;
7116              }
7117              $class = '';
7118              // hide/show link
7119              if (in_array($auth, $authsenabled)) {
7120                  $hideshow = "<a href=\"$url&amp;action=disable&amp;auth=$auth\">";
7121                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7122                  $enabled = true;
7123                  $displayname = $name;
7124              }
7125              else {
7126                  $hideshow = "<a href=\"$url&amp;action=enable&amp;auth=$auth\">";
7127                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7128                  $enabled = false;
7129                  $displayname = $name;
7130                  $class = 'dimmed_text';
7131              }
7132  
7133              $usercount = $DB->count_records('user', array('auth'=>$auth, 'deleted'=>0));
7134  
7135              // up/down link (only if auth is enabled)
7136              $updown = '';
7137              if ($enabled) {
7138                  if ($updowncount > 1) {
7139                      $updown .= "<a href=\"$url&amp;action=up&amp;auth=$auth\">";
7140                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7141                  }
7142                  else {
7143                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7144                  }
7145                  if ($updowncount < $authcount) {
7146                      $updown .= "<a href=\"$url&amp;action=down&amp;auth=$auth\">";
7147                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7148                  }
7149                  else {
7150                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7151                  }
7152                  ++ $updowncount;
7153              }
7154  
7155              // settings link
7156              if (file_exists($CFG->dirroot.'/auth/'.$auth.'/settings.php')) {
7157                  $settings = "<a href=\"settings.php?section=authsetting$auth\">{$txt->settings}</a>";
7158              } else if (file_exists($CFG->dirroot.'/auth/'.$auth.'/config.html')) {
7159                  $settings = "<a href=\"auth_config.php?auth=$auth\">{$txt->settings}</a>";
7160              } else {
7161                  $settings = '';
7162              }
7163  
7164              // Uninstall link.
7165              $uninstall = '';
7166              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('auth_'.$auth, 'manage')) {
7167                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7168              }
7169  
7170              $test = '';
7171              if (!empty($authplugins[$auth]) and method_exists($authplugins[$auth], 'test_settings')) {
7172                  $testurl = new moodle_url('/auth/test_settings.php', array('auth'=>$auth, 'sesskey'=>sesskey()));
7173                  $test = html_writer::link($testurl, $txt->testsettings);
7174              }
7175  
7176              // Add a row to the table.
7177              $row = new html_table_row(array($displayname, $usercount, $hideshow, $updown, $settings, $test, $uninstall));
7178              if ($class) {
7179                  $row->attributes['class'] = $class;
7180              }
7181              $table->data[] = $row;
7182          }
7183          $return .= html_writer::table($table);
7184          $return .= get_string('configauthenticationplugins', 'admin').'<br />'.get_string('tablenosave', 'filters');
7185          $return .= $OUTPUT->box_end();
7186          return highlight($query, $return);
7187      }
7188  }
7189  
7190  
7191  /**
7192   * Special class for authentication administration.
7193   *
7194   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7195   */
7196  class admin_setting_manageeditors extends admin_setting {
7197      /**
7198       * Calls parent::__construct with specific arguments
7199       */
7200      public function __construct() {
7201          $this->nosave = true;
7202          parent::__construct('editorsui', get_string('editorsettings', 'editor'), '', '');
7203      }
7204  
7205      /**
7206       * Always returns true, does nothing
7207       *
7208       * @return true
7209       */
7210      public function get_setting() {
7211          return true;
7212      }
7213  
7214      /**
7215       * Always returns true, does nothing
7216       *
7217       * @return true
7218       */
7219      public function get_defaultsetting() {
7220          return true;
7221      }
7222  
7223      /**
7224       * Always returns '', does not write anything
7225       *
7226       * @return string Always returns ''
7227       */
7228      public function write_setting($data) {
7229      // do not write any setting
7230          return '';
7231      }
7232  
7233      /**
7234       * Checks if $query is one of the available editors
7235       *
7236       * @param string $query The string to search for
7237       * @return bool Returns true if found, false if not
7238       */
7239      public function is_related($query) {
7240          if (parent::is_related($query)) {
7241              return true;
7242          }
7243  
7244          $editors_available = editors_get_available();
7245          foreach ($editors_available as $editor=>$editorstr) {
7246              if (strpos($editor, $query) !== false) {
7247                  return true;
7248              }
7249              if (strpos(core_text::strtolower($editorstr), $query) !== false) {
7250                  return true;
7251              }
7252          }
7253          return false;
7254      }
7255  
7256      /**
7257       * Builds the XHTML to display the control
7258       *
7259       * @param string $data Unused
7260       * @param string $query
7261       * @return string
7262       */
7263      public function output_html($data, $query='') {
7264          global $CFG, $OUTPUT;
7265  
7266          // display strings
7267          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7268              'up', 'down', 'none'));
7269          $struninstall = get_string('uninstallplugin', 'core_admin');
7270  
7271          $txt->updown = "$txt->up/$txt->down";
7272  
7273          $editors_available = editors_get_available();
7274          $active_editors = explode(',', $CFG->texteditors);
7275  
7276          $active_editors = array_reverse($active_editors);
7277          foreach ($active_editors as $key=>$editor) {
7278              if (empty($editors_available[$editor])) {
7279                  unset($active_editors[$key]);
7280              } else {
7281                  $name = $editors_available[$editor];
7282                  unset($editors_available[$editor]);
7283                  $editors_available[$editor] = $name;
7284              }
7285          }
7286          if (empty($active_editors)) {
7287          //$active_editors = array('textarea');
7288          }
7289          $editors_available = array_reverse($editors_available, true);
7290          $return = $OUTPUT->heading(get_string('acteditorshhdr', 'editor'), 3, 'main', true);
7291          $return .= $OUTPUT->box_start('generalbox editorsui');
7292  
7293          $table = new html_table();
7294          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7295          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7296          $table->id = 'editormanagement';
7297          $table->attributes['class'] = 'admintable generaltable';
7298          $table->data  = array();
7299  
7300          // iterate through auth plugins and add to the display table
7301          $updowncount = 1;
7302          $editorcount = count($active_editors);
7303          $url = "editors.php?sesskey=" . sesskey();
7304          foreach ($editors_available as $editor => $name) {
7305          // hide/show link
7306              $class = '';
7307              if (in_array($editor, $active_editors)) {
7308                  $hideshow = "<a href=\"$url&amp;action=disable&amp;editor=$editor\">";
7309                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7310                  $enabled = true;
7311                  $displayname = $name;
7312              }
7313              else {
7314                  $hideshow = "<a href=\"$url&amp;action=enable&amp;editor=$editor\">";
7315                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7316                  $enabled = false;
7317                  $displayname = $name;
7318                  $class = 'dimmed_text';
7319              }
7320  
7321              // up/down link (only if auth is enabled)
7322              $updown = '';
7323              if ($enabled) {
7324                  if ($updowncount > 1) {
7325                      $updown .= "<a href=\"$url&amp;action=up&amp;editor=$editor\">";
7326                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7327                  }
7328                  else {
7329                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7330                  }
7331                  if ($updowncount < $editorcount) {
7332                      $updown .= "<a href=\"$url&amp;action=down&amp;editor=$editor\">";
7333                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7334                  }
7335                  else {
7336                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7337                  }
7338                  ++ $updowncount;
7339              }
7340  
7341              // settings link
7342              if (file_exists($CFG->dirroot.'/lib/editor/'.$editor.'/settings.php')) {
7343                  $eurl = new moodle_url('/admin/settings.php', array('section'=>'editorsettings'.$editor));
7344                  $settings = "<a href='$eurl'>{$txt->settings}</a>";
7345              } else {
7346                  $settings = '';
7347              }
7348  
7349              $uninstall = '';
7350              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('editor_'.$editor, 'manage')) {
7351                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7352              }
7353  
7354              // Add a row to the table.
7355              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7356              if ($class) {
7357                  $row->attributes['class'] = $class;
7358              }
7359              $table->data[] = $row;
7360          }
7361          $return .= html_writer::table($table);
7362          $return .= get_string('configeditorplugins', 'editor').'<br />'.get_string('tablenosave', 'admin');
7363          $return .= $OUTPUT->box_end();
7364          return highlight($query, $return);
7365      }
7366  }
7367  
7368  /**
7369   * Special class for antiviruses administration.
7370   *
7371   * @copyright  2015 Ruslan Kabalin, Lancaster University.
7372   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7373   */
7374  class admin_setting_manageantiviruses extends admin_setting {
7375      /**
7376       * Calls parent::__construct with specific arguments
7377       */
7378      public function __construct() {
7379          $this->nosave = true;
7380          parent::__construct('antivirusesui', get_string('antivirussettings', 'antivirus'), '', '');
7381      }
7382  
7383      /**
7384       * Always returns true, does nothing
7385       *
7386       * @return true
7387       */
7388      public function get_setting() {
7389          return true;
7390      }
7391  
7392      /**
7393       * Always returns true, does nothing
7394       *
7395       * @return true
7396       */
7397      public function get_defaultsetting() {
7398          return true;
7399      }
7400  
7401      /**
7402       * Always returns '', does not write anything
7403       *
7404       * @param string $data Unused
7405       * @return string Always returns ''
7406       */
7407      public function write_setting($data) {
7408          // Do not write any setting.
7409          return '';
7410      }
7411  
7412      /**
7413       * Checks if $query is one of the available editors
7414       *
7415       * @param string $query The string to search for
7416       * @return bool Returns true if found, false if not
7417       */
7418      public function is_related($query) {
7419          if (parent::is_related($query)) {
7420              return true;
7421          }
7422  
7423          $antivirusesavailable = \core\antivirus\manager::get_available();
7424          foreach ($antivirusesavailable as $antivirus => $antivirusstr) {
7425              if (strpos($antivirus, $query) !== false) {
7426                  return true;
7427              }
7428              if (strpos(core_text::strtolower($antivirusstr), $query) !== false) {
7429                  return true;
7430              }
7431          }
7432          return false;
7433      }
7434  
7435      /**
7436       * Builds the XHTML to display the control
7437       *
7438       * @param string $data Unused
7439       * @param string $query
7440       * @return string
7441       */
7442      public function output_html($data, $query='') {
7443          global $CFG, $OUTPUT;
7444  
7445          // Display strings.
7446          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7447              'up', 'down', 'none'));
7448          $struninstall = get_string('uninstallplugin', 'core_admin');
7449  
7450          $txt->updown = "$txt->up/$txt->down";
7451  
7452          $antivirusesavailable = \core\antivirus\manager::get_available();
7453          $activeantiviruses = explode(',', $CFG->antiviruses);
7454  
7455          $activeantiviruses = array_reverse($activeantiviruses);
7456          foreach ($activeantiviruses as $key => $antivirus) {
7457              if (empty($antivirusesavailable[$antivirus])) {
7458                  unset($activeantiviruses[$key]);
7459              } else {
7460                  $name = $antivirusesavailable[$antivirus];
7461                  unset($antivirusesavailable[$antivirus]);
7462                  $antivirusesavailable[$antivirus] = $name;
7463              }
7464          }
7465          $antivirusesavailable = array_reverse($antivirusesavailable, true);
7466          $return = $OUTPUT->heading(get_string('actantivirushdr', 'antivirus'), 3, 'main', true);
7467          $return .= $OUTPUT->box_start('generalbox antivirusesui');
7468  
7469          $table = new html_table();
7470          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7471          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7472          $table->id = 'antivirusmanagement';
7473          $table->attributes['class'] = 'admintable generaltable';
7474          $table->data  = array();
7475  
7476          // Iterate through auth plugins and add to the display table.
7477          $updowncount = 1;
7478          $antiviruscount = count($activeantiviruses);
7479          $baseurl = new moodle_url('/admin/antiviruses.php', array('sesskey' => sesskey()));
7480          foreach ($antivirusesavailable as $antivirus => $name) {
7481              // Hide/show link.
7482              $class = '';
7483              if (in_array($antivirus, $activeantiviruses)) {
7484                  $hideshowurl = $baseurl;
7485                  $hideshowurl->params(array('action' => 'disable', 'antivirus' => $antivirus));
7486                  $hideshowimg = $OUTPUT->pix_icon('t/hide', get_string('disable'));
7487                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7488                  $enabled = true;
7489                  $displayname = $name;
7490              } else {
7491                  $hideshowurl = $baseurl;
7492                  $hideshowurl->params(array('action' => 'enable', 'antivirus' => $antivirus));
7493                  $hideshowimg = $OUTPUT->pix_icon('t/show', get_string('enable'));
7494                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7495                  $enabled = false;
7496                  $displayname = $name;
7497                  $class = 'dimmed_text';
7498              }
7499  
7500              // Up/down link.
7501              $updown = '';
7502              if ($enabled) {
7503                  if ($updowncount > 1) {
7504                      $updownurl = $baseurl;
7505                      $updownurl->params(array('action' => 'up', 'antivirus' => $antivirus));
7506                      $updownimg = $OUTPUT->pix_icon('t/up', get_string('moveup'));
7507                      $updown = html_writer::link($updownurl, $updownimg);
7508                  } else {
7509                      $updownimg = $OUTPUT->spacer();
7510                  }
7511                  if ($updowncount < $antiviruscount) {
7512                      $updownurl = $baseurl;
7513                      $updownurl->params(array('action' => 'down', 'antivirus' => $antivirus));
7514                      $updownimg = $OUTPUT->pix_icon('t/down', get_string('movedown'));
7515                      $updown = html_writer::link($updownurl, $updownimg);
7516                  } else {
7517                      $updownimg = $OUTPUT->spacer();
7518                  }
7519                  ++ $updowncount;
7520              }
7521  
7522              // Settings link.
7523              if (file_exists($CFG->dirroot.'/lib/antivirus/'.$antivirus.'/settings.php')) {
7524                  $eurl = new moodle_url('/admin/settings.php', array('section' => 'antivirussettings'.$antivirus));
7525                  $settings = html_writer::link($eurl, $txt->settings);
7526              } else {
7527                  $settings = '';
7528              }
7529  
7530              $uninstall = '';
7531              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('antivirus_'.$antivirus, 'manage')) {
7532                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7533              }
7534  
7535              // Add a row to the table.
7536              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7537              if ($class) {
7538                  $row->attributes['class'] = $class;
7539              }
7540              $table->data[] = $row;
7541          }
7542          $return .= html_writer::table($table);
7543          $return .= get_string('configantivirusplugins', 'antivirus') . html_writer::empty_tag('br') . get_string('tablenosave', 'admin');
7544          $return .= $OUTPUT->box_end();
7545          return highlight($query, $return);
7546      }
7547  }
7548  
7549  /**
7550   * Special class for license administration.
7551   *
7552   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7553   * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7554   * @todo MDL-45184 This class will be deleted in Moodle 4.1.
7555   */
7556  class admin_setting_managelicenses extends admin_setting {
7557      /**
7558       * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7559       * @todo MDL-45184 This class will be deleted in Moodle 4.1
7560       */
7561      public function __construct() {
7562          global $ADMIN;
7563  
7564          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7565              DEBUG_DEVELOPER);
7566  
7567          // Replace admin setting load with new external page load for tool_licensemanager, if not loaded already.
7568          if (!is_null($ADMIN->locate('licensemanager'))) {
7569              $temp = new admin_externalpage('licensemanager',
7570                  get_string('licensemanager', 'tool_licensemanager'),
7571                  \tool_licensemanager\helper::get_licensemanager_url());
7572  
7573              $ADMIN->add('license', $temp);
7574          }
7575      }
7576  
7577      /**
7578       * Always returns true, does nothing
7579       *
7580       * @deprecated since Moodle 3.9 MDL-45184.
7581       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7582       *
7583       * @return true
7584       */
7585      public function get_setting() {
7586          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7587              DEBUG_DEVELOPER);
7588  
7589          return true;
7590      }
7591  
7592      /**
7593       * Always returns true, does nothing
7594       *
7595       * @deprecated since Moodle 3.9 MDL-45184.
7596       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7597       *
7598       * @return true
7599       */
7600      public function get_defaultsetting() {
7601          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7602              DEBUG_DEVELOPER);
7603  
7604          return true;
7605      }
7606  
7607      /**
7608       * Always returns '', does not write anything
7609       *
7610       * @deprecated since Moodle 3.9 MDL-45184.
7611       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7612       *
7613       * @return string Always returns ''
7614       */
7615      public function write_setting($data) {
7616          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7617              DEBUG_DEVELOPER);
7618  
7619          // do not write any setting
7620          return '';
7621      }
7622  
7623      /**
7624       * Builds the XHTML to display the control
7625       *
7626       * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7627       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7628       *
7629       * @param string $data Unused
7630       * @param string $query
7631       * @return string
7632       */
7633      public function output_html($data, $query='') {
7634          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7635              DEBUG_DEVELOPER);
7636  
7637          redirect(\tool_licensemanager\helper::get_licensemanager_url());
7638      }
7639  }
7640  
7641  /**
7642   * Course formats manager. Allows to enable/disable formats and jump to settings
7643   */
7644  class admin_setting_manageformats extends admin_setting {
7645  
7646      /**
7647       * Calls parent::__construct with specific arguments
7648       */
7649      public function __construct() {
7650          $this->nosave = true;
7651          parent::__construct('formatsui', new lang_string('manageformats', 'core_admin'), '', '');
7652      }
7653  
7654      /**
7655       * Always returns true
7656       *
7657       * @return true
7658       */
7659      public function get_setting() {
7660          return true;
7661      }
7662  
7663      /**
7664       * Always returns true
7665       *
7666       * @return true
7667       */
7668      public function get_defaultsetting() {
7669          return true;
7670      }
7671  
7672      /**
7673       * Always returns '' and doesn't write anything
7674       *
7675       * @param mixed $data string or array, must not be NULL
7676       * @return string Always returns ''
7677       */
7678      public function write_setting($data) {
7679          // do not write any setting
7680          return '';
7681      }
7682  
7683      /**
7684       * Search to find if Query is related to format plugin
7685       *
7686       * @param string $query The string to search for
7687       * @return bool true for related false for not
7688       */
7689      public function is_related($query) {
7690          if (parent::is_related($query)) {
7691              return true;
7692          }
7693          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7694          foreach ($formats as $format) {
7695              if (strpos($format->component, $query) !== false ||
7696                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7697                  return true;
7698              }
7699          }
7700          return false;
7701      }
7702  
7703      /**
7704       * Return XHTML to display control
7705       *
7706       * @param mixed $data Unused
7707       * @param string $query
7708       * @return string highlight
7709       */
7710      public function output_html($data, $query='') {
7711          global $CFG, $OUTPUT;
7712          $return = '';
7713          $return = $OUTPUT->heading(new lang_string('courseformats'), 3, 'main');
7714          $return .= $OUTPUT->box_start('generalbox formatsui');
7715  
7716          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7717  
7718          // display strings
7719          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
7720          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7721          $txt->updown = "$txt->up/$txt->down";
7722  
7723          $table = new html_table();
7724          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
7725          $table->align = array('left', 'center', 'center', 'center', 'center');
7726          $table->attributes['class'] = 'manageformattable generaltable admintable';
7727          $table->data  = array();
7728  
7729          $cnt = 0;
7730          $defaultformat = get_config('moodlecourse', 'format');
7731          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7732          foreach ($formats as $format) {
7733              $url = new moodle_url('/admin/courseformats.php',
7734                      array('sesskey' => sesskey(), 'format' => $format->name));
7735              $isdefault = '';
7736              $class = '';
7737              if ($format->is_enabled()) {
7738                  $strformatname = $format->displayname;
7739                  if ($defaultformat === $format->name) {
7740                      $hideshow = $txt->default;
7741                  } else {
7742                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7743                              $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7744                  }
7745              } else {
7746                  $strformatname = $format->displayname;
7747                  $class = 'dimmed_text';
7748                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7749                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7750              }
7751              $updown = '';
7752              if ($cnt) {
7753                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
7754                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
7755              } else {
7756                  $updown .= $spacer;
7757              }
7758              if ($cnt < count($formats) - 1) {
7759                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
7760                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
7761              } else {
7762                  $updown .= $spacer;
7763              }
7764              $cnt++;
7765              $settings = '';
7766              if ($format->get_settings_url()) {
7767                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
7768              }
7769              $uninstall = '';
7770              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('format_'.$format->name, 'manage')) {
7771                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7772              }
7773              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
7774              if ($class) {
7775                  $row->attributes['class'] = $class;
7776              }
7777              $table->data[] = $row;
7778          }
7779          $return .= html_writer::table($table);
7780          $link = html_writer::link(new moodle_url('/admin/settings.php', array('section' => 'coursesettings')), new lang_string('coursesettings'));
7781          $return .= html_writer::tag('p', get_string('manageformatsgotosettings', 'admin', $link));
7782          $return .= $OUTPUT->box_end();
7783          return highlight($query, $return);
7784      }
7785  }
7786  
7787  /**
7788   * Custom fields manager. Allows to enable/disable custom fields and jump to settings.
7789   *
7790   * @package    core
7791   * @copyright  2018 Toni Barbera
7792   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7793   */
7794  class admin_setting_managecustomfields extends admin_setting {
7795  
7796      /**
7797       * Calls parent::__construct with specific arguments
7798       */
7799      public function __construct() {
7800          $this->nosave = true;
7801          parent::__construct('customfieldsui', new lang_string('managecustomfields', 'core_admin'), '', '');
7802      }
7803  
7804      /**
7805       * Always returns true
7806       *
7807       * @return true
7808       */
7809      public function get_setting() {
7810          return true;
7811      }
7812  
7813      /**
7814       * Always returns true
7815       *
7816       * @return true
7817       */
7818      public function get_defaultsetting() {
7819          return true;
7820      }
7821  
7822      /**
7823       * Always returns '' and doesn't write anything
7824       *
7825       * @param mixed $data string or array, must not be NULL
7826       * @return string Always returns ''
7827       */
7828      public function write_setting($data) {
7829          // Do not write any setting.
7830          return '';
7831      }
7832  
7833      /**
7834       * Search to find if Query is related to format plugin
7835       *
7836       * @param string $query The string to search for
7837       * @return bool true for related false for not
7838       */
7839      public function is_related($query) {
7840          if (parent::is_related($query)) {
7841              return true;
7842          }
7843          $formats = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7844          foreach ($formats as $format) {
7845              if (strpos($format->component, $query) !== false ||
7846                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7847                  return true;
7848              }
7849          }
7850          return false;
7851      }
7852  
7853      /**
7854       * Return XHTML to display control
7855       *
7856       * @param mixed $data Unused
7857       * @param string $query
7858       * @return string highlight
7859       */
7860      public function output_html($data, $query='') {
7861          global $CFG, $OUTPUT;
7862          $return = '';
7863          $return = $OUTPUT->heading(new lang_string('customfields', 'core_customfield'), 3, 'main');
7864          $return .= $OUTPUT->box_start('generalbox customfieldsui');
7865  
7866          $fields = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7867  
7868          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down'));
7869          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7870          $txt->updown = "$txt->up/$txt->down";
7871  
7872          $table = new html_table();
7873          $table->head  = array($txt->name, $txt->enable, $txt->uninstall, $txt->settings);
7874          $table->align = array('left', 'center', 'center', 'center');
7875          $table->attributes['class'] = 'managecustomfieldtable generaltable admintable';
7876          $table->data  = array();
7877  
7878          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7879          foreach ($fields as $field) {
7880              $url = new moodle_url('/admin/customfields.php',
7881                      array('sesskey' => sesskey(), 'field' => $field->name));
7882  
7883              if ($field->is_enabled()) {
7884                  $strfieldname = $field->displayname;
7885                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7886                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7887              } else {
7888                  $strfieldname = $field->displayname;
7889                  $class = 'dimmed_text';
7890                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7891                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7892              }
7893              $settings = '';
7894              if ($field->get_settings_url()) {
7895                  $settings = html_writer::link($field->get_settings_url(), $txt->settings);
7896              }
7897              $uninstall = '';
7898              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('customfield_'.$field->name, 'manage')) {
7899                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7900              }
7901              $row = new html_table_row(array($strfieldname, $hideshow, $uninstall, $settings));
7902              $table->data[] = $row;
7903          }
7904          $return .= html_writer::table($table);
7905          $return .= $OUTPUT->box_end();
7906          return highlight($query, $return);
7907      }
7908  }
7909  
7910  /**
7911   * Data formats manager. Allow reorder and to enable/disable data formats and jump to settings
7912   *
7913   * @copyright  2016 Brendan Heywood (brendan@catalyst-au.net)
7914   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7915   */
7916  class admin_setting_managedataformats extends admin_setting {
7917  
7918      /**
7919       * Calls parent::__construct with specific arguments
7920       */
7921      public function __construct() {
7922          $this->nosave = true;
7923          parent::__construct('managedataformats', new lang_string('managedataformats'), '', '');
7924      }
7925  
7926      /**
7927       * Always returns true
7928       *
7929       * @return true
7930       */
7931      public function get_setting() {
7932          return true;
7933      }
7934  
7935      /**
7936       * Always returns true
7937       *
7938       * @return true
7939       */
7940      public function get_defaultsetting() {
7941          return true;
7942      }
7943  
7944      /**
7945       * Always returns '' and doesn't write anything
7946       *
7947       * @param mixed $data string or array, must not be NULL
7948       * @return string Always returns ''
7949       */
7950      public function write_setting($data) {
7951          // Do not write any setting.
7952          return '';
7953      }
7954  
7955      /**
7956       * Search to find if Query is related to format plugin
7957       *
7958       * @param string $query The string to search for
7959       * @return bool true for related false for not
7960       */
7961      public function is_related($query) {
7962          if (parent::is_related($query)) {
7963              return true;
7964          }
7965          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
7966          foreach ($formats as $format) {
7967              if (strpos($format->component, $query) !== false ||
7968                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7969                  return true;
7970              }
7971          }
7972          return false;
7973      }
7974  
7975      /**
7976       * Return XHTML to display control
7977       *
7978       * @param mixed $data Unused
7979       * @param string $query
7980       * @return string highlight
7981       */
7982      public function output_html($data, $query='') {
7983          global $CFG, $OUTPUT;
7984          $return = '';
7985  
7986          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
7987  
7988          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
7989          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7990          $txt->updown = "$txt->up/$txt->down";
7991  
7992          $table = new html_table();
7993          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
7994          $table->align = array('left', 'center', 'center', 'center', 'center');
7995          $table->attributes['class'] = 'manageformattable generaltable admintable';
7996          $table->data  = array();
7997  
7998          $cnt = 0;
7999          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8000          $totalenabled = 0;
8001          foreach ($formats as $format) {
8002              if ($format->is_enabled() && $format->is_installed_and_upgraded()) {
8003                  $totalenabled++;
8004              }
8005          }
8006          foreach ($formats as $format) {
8007              $status = $format->get_status();
8008              $url = new moodle_url('/admin/dataformats.php',
8009                      array('sesskey' => sesskey(), 'name' => $format->name));
8010  
8011              $class = '';
8012              if ($format->is_enabled()) {
8013                  $strformatname = $format->displayname;
8014                  if ($totalenabled == 1&& $format->is_enabled()) {
8015                      $hideshow = '';
8016                  } else {
8017                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
8018                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
8019                  }
8020              } else {
8021                  $class = 'dimmed_text';
8022                  $strformatname = $format->displayname;
8023                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
8024                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
8025              }
8026  
8027              $updown = '';
8028              if ($cnt) {
8029                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
8030                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
8031              } else {
8032                  $updown .= $spacer;
8033              }
8034              if ($cnt < count($formats) - 1) {
8035                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
8036                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
8037              } else {
8038                  $updown .= $spacer;
8039              }
8040  
8041              $uninstall = '';
8042              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
8043                  $uninstall = get_string('status_missing', 'core_plugin');
8044              } else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
8045                  $uninstall = get_string('status_new', 'core_plugin');
8046              } else if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('dataformat_'.$format->name, 'manage')) {
8047                  if ($totalenabled != 1 || !$format->is_enabled()) {
8048                      $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
8049                  }
8050              }
8051  
8052              $settings = '';
8053              if ($format->get_settings_url()) {
8054                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
8055              }
8056  
8057              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
8058              if ($class) {
8059                  $row->attributes['class'] = $class;
8060              }
8061              $table->data[] = $row;
8062              $cnt++;
8063          }
8064          $return .= html_writer::table($table);
8065          return highlight($query, $return);
8066      }
8067  }
8068  
8069  /**
8070   * Special class for filter administration.
8071   *
8072   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8073   */
8074  class admin_page_managefilters extends admin_externalpage {
8075      /**
8076       * Calls parent::__construct with specific arguments
8077       */
8078      public function __construct() {
8079          global $CFG;
8080          parent::__construct('managefilters', get_string('filtersettings', 'admin'), "$CFG->wwwroot/$CFG->admin/filters.php");
8081      }
8082  
8083      /**
8084       * Searches all installed filters for specified filter
8085       *
8086       * @param string $query The filter(string) to search for
8087       * @param string $query
8088       */
8089      public function search($query) {
8090          global $CFG;
8091          if ($result = parent::search($query)) {
8092              return $result;
8093          }
8094  
8095          $found = false;
8096          $filternames = filter_get_all_installed();
8097          foreach ($filternames as $path => $strfiltername) {
8098              if (strpos(core_text::strtolower($strfiltername), $query) !== false) {
8099                  $found = true;
8100                  break;
8101              }
8102              if (strpos($path, $query) !== false) {
8103                  $found = true;
8104                  break;
8105              }
8106          }
8107  
8108          if ($found) {
8109              $result = new stdClass;
8110              $result->page = $this;
8111              $result->settings = array();
8112              return array($this->name => $result);
8113          } else {
8114              return array();
8115          }
8116      }
8117  }
8118  
8119  /**
8120   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8121   * Requires a get_rank method on the plugininfo class for sorting.
8122   *
8123   * @copyright 2017 Damyon Wiese
8124   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8125   */
8126  abstract class admin_setting_manage_plugins extends admin_setting {
8127  
8128      /**
8129       * Get the admin settings section name (just a unique string)
8130       *
8131       * @return string
8132       */
8133      public function get_section_name() {
8134          return 'manage' . $this->get_plugin_type() . 'plugins';
8135      }
8136  
8137      /**
8138       * Get the admin settings section title (use get_string).
8139       *
8140       * @return string
8141       */
8142      abstract public function get_section_title();
8143  
8144      /**
8145       * Get the type of plugin to manage.
8146       *
8147       * @return string
8148       */
8149      abstract public function get_plugin_type();
8150  
8151      /**
8152       * Get the name of the second column.
8153       *
8154       * @return string
8155       */
8156      public function get_info_column_name() {
8157          return '';
8158      }
8159  
8160      /**
8161       * Get the type of plugin to manage.
8162       *
8163       * @param plugininfo The plugin info class.
8164       * @return string
8165       */
8166      abstract public function get_info_column($plugininfo);
8167  
8168      /**
8169       * Calls parent::__construct with specific arguments
8170       */
8171      public function __construct() {
8172          $this->nosave = true;
8173          parent::__construct($this->get_section_name(), $this->get_section_title(), '', '');
8174      }
8175  
8176      /**
8177       * Always returns true, does nothing
8178       *
8179       * @return true
8180       */
8181      public function get_setting() {
8182          return true;
8183      }
8184  
8185      /**
8186       * Always returns true, does nothing
8187       *
8188       * @return true
8189       */
8190      public function get_defaultsetting() {
8191          return true;
8192      }
8193  
8194      /**
8195       * Always returns '', does not write anything
8196       *
8197       * @param mixed $data
8198       * @return string Always returns ''
8199       */
8200      public function write_setting($data) {
8201          // Do not write any setting.
8202          return '';
8203      }
8204  
8205      /**
8206       * Checks if $query is one of the available plugins of this type
8207       *
8208       * @param string $query The string to search for
8209       * @return bool Returns true if found, false if not
8210       */
8211      public function is_related($query) {
8212          if (parent::is_related($query)) {
8213              return true;
8214          }
8215  
8216          $query = core_text::strtolower($query);
8217          $plugins = core_plugin_manager::instance()->get_plugins_of_type($this->get_plugin_type());
8218          foreach ($plugins as $name => $plugin) {
8219              $localised = $plugin->displayname;
8220              if (strpos(core_text::strtolower($name), $query) !== false) {
8221                  return true;
8222              }
8223              if (strpos(core_text::strtolower($localised), $query) !== false) {
8224                  return true;
8225              }
8226          }
8227          return false;
8228      }
8229  
8230      /**
8231       * The URL for the management page for this plugintype.
8232       *
8233       * @return moodle_url
8234       */
8235      protected function get_manage_url() {
8236          return new moodle_url('/admin/updatesetting.php');
8237      }
8238  
8239      /**
8240       * Builds the HTML to display the control.
8241       *
8242       * @param string $data Unused
8243       * @param string $query
8244       * @return string
8245       */
8246      public function output_html($data, $query = '') {
8247          global $CFG, $OUTPUT, $DB, $PAGE;
8248  
8249          $context = (object) [
8250              'manageurl' => new moodle_url($this->get_manage_url(), [
8251                      'type' => $this->get_plugin_type(),
8252                      'sesskey' => sesskey(),
8253                  ]),
8254              'infocolumnname' => $this->get_info_column_name(),
8255              'plugins' => [],
8256          ];
8257  
8258          $pluginmanager = core_plugin_manager::instance();
8259          $allplugins = $pluginmanager->get_plugins_of_type($this->get_plugin_type());
8260          $enabled = $pluginmanager->get_enabled_plugins($this->get_plugin_type());
8261          $plugins = array_merge($enabled, $allplugins);
8262          foreach ($plugins as $key => $plugin) {
8263              $pluginlink = new moodle_url($context->manageurl, ['plugin' => $key]);
8264  
8265              $pluginkey = (object) [
8266                  'plugin' => $plugin->displayname,
8267                  'enabled' => $plugin->is_enabled(),
8268                  'togglelink' => '',
8269                  'moveuplink' => '',
8270                  'movedownlink' => '',
8271                  'settingslink' => $plugin->get_settings_url(),
8272                  'uninstalllink' => '',
8273                  'info' => '',
8274              ];
8275  
8276              // Enable/Disable link.
8277              $togglelink = new moodle_url($pluginlink);
8278              if ($plugin->is_enabled()) {
8279                  $toggletarget = false;
8280                  $togglelink->param('action', 'disable');
8281  
8282                  if (count($context->plugins)) {
8283                      // This is not the first plugin.
8284                      $pluginkey->moveuplink = new moodle_url($pluginlink, ['action' => 'up']);
8285                  }
8286  
8287                  if (count($enabled) > count($context->plugins) + 1) {
8288                      // This is not the last plugin.
8289                      $pluginkey->movedownlink = new moodle_url($pluginlink, ['action' => 'down']);
8290                  }
8291  
8292                  $pluginkey->info = $this->get_info_column($plugin);
8293              } else {
8294                  $toggletarget = true;
8295                  $togglelink->param('action', 'enable');
8296              }
8297  
8298              $pluginkey->toggletarget = $toggletarget;
8299              $pluginkey->togglelink = $togglelink;
8300  
8301              $frankenstyle = $plugin->type . '_' . $plugin->name;
8302              if ($uninstalllink = core_plugin_manager::instance()->get_uninstall_url($frankenstyle, 'manage')) {
8303                  // This plugin supports uninstallation.
8304                  $pluginkey->uninstalllink = $uninstalllink;
8305              }
8306  
8307              if (!empty($this->get_info_column_name())) {
8308                  // This plugintype has an info column.
8309                  $pluginkey->info = $this->get_info_column($plugin);
8310              }
8311  
8312              $context->plugins[] = $pluginkey;
8313          }
8314  
8315          $str = $OUTPUT->render_from_template('core_admin/setting_manage_plugins', $context);
8316          return highlight($query, $str);
8317      }
8318  }
8319  
8320  /**
8321   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8322   * Requires a get_rank method on the plugininfo class for sorting.
8323   *
8324   * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
8325  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8326   */
8327  class admin_setting_manage_fileconverter_plugins extends admin_setting_manage_plugins {
8328      public function get_section_title() {
8329          return get_string('type_fileconverter_plural', 'plugin');
8330      }
8331  
8332      public function get_plugin_type() {
8333          return 'fileconverter';
8334      }
8335  
8336      public function get_info_column_name() {
8337          return get_string('supportedconversions', 'plugin');
8338      }
8339  
8340      public function get_info_column($plugininfo) {
8341          return $plugininfo->get_supported_conversions();
8342      }
8343  }
8344  
8345  /**
8346   * Special class for media player plugins management.
8347   *
8348   * @copyright 2016 Marina Glancy
8349   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8350   */
8351  class admin_setting_managemediaplayers extends admin_setting {
8352      /**
8353       * Calls parent::__construct with specific arguments
8354       */
8355      public function __construct() {
8356          $this->nosave = true;
8357          parent::__construct('managemediaplayers', get_string('managemediaplayers', 'media'), '', '');
8358      }
8359  
8360      /**
8361       * Always returns true, does nothing
8362       *
8363       * @return true
8364       */
8365      public function get_setting() {
8366          return true;
8367      }
8368  
8369      /**
8370       * Always returns true, does nothing
8371       *
8372       * @return true
8373       */
8374      public function get_defaultsetting() {
8375          return true;
8376      }
8377  
8378      /**
8379       * Always returns '', does not write anything
8380       *
8381       * @param mixed $data
8382       * @return string Always returns ''
8383       */
8384      public function write_setting($data) {
8385          // Do not write any setting.
8386          return '';
8387      }
8388  
8389      /**
8390       * Checks if $query is one of the available enrol plugins
8391       *
8392       * @param string $query The string to search for
8393       * @return bool Returns true if found, false if not
8394       */
8395      public function is_related($query) {
8396          if (parent::is_related($query)) {
8397              return true;
8398          }
8399  
8400          $query = core_text::strtolower($query);
8401          $plugins = core_plugin_manager::instance()->get_plugins_of_type('media');
8402          foreach ($plugins as $name => $plugin) {
8403              $localised = $plugin->displayname;
8404              if (strpos(core_text::strtolower($name), $query) !== false) {
8405                  return true;
8406              }
8407              if (strpos(core_text::strtolower($localised), $query) !== false) {
8408                  return true;
8409              }
8410          }
8411          return false;
8412      }
8413  
8414      /**
8415       * Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8416       * @return \core\plugininfo\media[]
8417       */
8418      protected function get_sorted_plugins() {
8419          $pluginmanager = core_plugin_manager::instance();
8420  
8421          $plugins = $pluginmanager->get_plugins_of_type('media');
8422          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8423  
8424          // Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8425          \core_collator::asort_objects_by_method($plugins, 'get_rank', \core_collator::SORT_NUMERIC);
8426  
8427          $order = array_values($enabledplugins);
8428          $order = array_merge($order, array_diff(array_reverse(array_keys($plugins)), $order));
8429  
8430          $sortedplugins = array();
8431          foreach ($order as $name) {
8432              $sortedplugins[$name] = $plugins[$name];
8433          }
8434  
8435          return $sortedplugins;
8436      }
8437  
8438      /**
8439       * Builds the XHTML to display the control
8440       *
8441       * @param string $data Unused
8442       * @param string $query
8443       * @return string
8444       */
8445      public function output_html($data, $query='') {
8446          global $CFG, $OUTPUT, $DB, $PAGE;
8447  
8448          // Display strings.
8449          $strup        = get_string('up');
8450          $strdown      = get_string('down');
8451          $strsettings  = get_string('settings');
8452          $strenable    = get_string('enable');
8453          $strdisable   = get_string('disable');
8454          $struninstall = get_string('uninstallplugin', 'core_admin');
8455          $strversion   = get_string('version');
8456          $strname      = get_string('name');
8457          $strsupports  = get_string('supports', 'core_media');
8458  
8459          $pluginmanager = core_plugin_manager::instance();
8460  
8461          $plugins = $this->get_sorted_plugins();
8462          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8463  
8464          $return = $OUTPUT->box_start('generalbox mediaplayersui');
8465  
8466          $table = new html_table();
8467          $table->head  = array($strname, $strsupports, $strversion,
8468              $strenable, $strup.'/'.$strdown, $strsettings, $struninstall);
8469          $table->colclasses = array('leftalign', 'leftalign', 'centeralign',
8470              'centeralign', 'centeralign', 'centeralign', 'centeralign');
8471          $table->id = 'mediaplayerplugins';
8472          $table->attributes['class'] = 'admintable generaltable';
8473          $table->data  = array();
8474  
8475          // Iterate through media plugins and add to the display table.
8476          $updowncount = 1;
8477          $url = new moodle_url('/admin/media.php', array('sesskey' => sesskey()));
8478          $printed = array();
8479          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8480  
8481          $usedextensions = [];
8482          foreach ($plugins as $name => $plugin) {
8483              $url->param('media', $name);
8484              $plugininfo = $pluginmanager->get_plugin_info('media_'.$name);
8485              $version = $plugininfo->versiondb;
8486              $supports = $plugininfo->supports($usedextensions);
8487  
8488              // Hide/show links.
8489              $class = '';
8490              if (!$plugininfo->is_installed_and_upgraded()) {
8491                  $hideshow = '';
8492                  $enabled = false;
8493                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
8494              } else {
8495                  $enabled = $plugininfo->is_enabled();
8496                  if ($enabled) {
8497                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'disable')),
8498                          $OUTPUT->pix_icon('t/hide', $strdisable, 'moodle', array('class' => 'iconsmall')));
8499                  } else {
8500                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'enable')),
8501                          $OUTPUT->pix_icon('t/show', $strenable, 'moodle', array('class' => 'iconsmall')));
8502                      $class = 'dimmed_text';
8503                  }
8504                  $displayname = $plugin->displayname;
8505                  if (get_string_manager()->string_exists('pluginname_help', 'media_' . $name)) {
8506                      $displayname .= '&nbsp;' . $OUTPUT->help_icon('pluginname', 'media_' . $name);
8507                  }
8508              }
8509              if ($PAGE->theme->resolve_image_location('icon', 'media_' . $name, false)) {
8510                  $icon = $OUTPUT->pix_icon('icon', '', 'media_' . $name, array('class' => 'icon pluginicon'));
8511              } else {
8512                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
8513              }
8514  
8515              // Up/down link (only if enrol is enabled).
8516              $updown = '';
8517              if ($enabled) {
8518                  if ($updowncount > 1) {
8519                      $updown = html_writer::link(new moodle_url($url, array('action' => 'up')),
8520                          $OUTPUT->pix_icon('t/up', $strup, 'moodle', array('class' => 'iconsmall')));
8521                  } else {
8522                      $updown = $spacer;
8523                  }
8524                  if ($updowncount < count($enabledplugins)) {
8525                      $updown .= html_writer::link(new moodle_url($url, array('action' => 'down')),
8526                          $OUTPUT->pix_icon('t/down', $strdown, 'moodle', array('class' => 'iconsmall')));
8527                  } else {
8528                      $updown .= $spacer;
8529                  }
8530                  ++$updowncount;
8531              }
8532  
8533              $uninstall = '';
8534              $status = $plugininfo->get_status();
8535              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
8536                  $uninstall = get_string('status_missing', 'core_plugin') . '<br/>';
8537              }
8538              if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
8539                  $uninstall = get_string('status_new', 'core_plugin');
8540              } else if ($uninstallurl = $pluginmanager->get_uninstall_url('media_'.$name, 'manage')) {
8541                  $uninstall .= html_writer::link($uninstallurl, $struninstall);
8542              }
8543  
8544              $settings = '';
8545              if ($plugininfo->get_settings_url()) {
8546                  $settings = html_writer::link($plugininfo->get_settings_url(), $strsettings);
8547              }
8548  
8549              // Add a row to the table.
8550              $row = new html_table_row(array($icon.$displayname, $supports, $version, $hideshow, $updown, $settings, $uninstall));
8551              if ($class) {
8552                  $row->attributes['class'] = $class;
8553              }
8554              $table->data[] = $row;
8555  
8556              $printed[$name] = true;
8557          }
8558  
8559          $return .= html_writer::table($table);
8560          $return .= $OUTPUT->box_end();
8561          return highlight($query, $return);
8562      }
8563  }
8564  
8565  
8566  /**
8567   * Content bank content types manager. Allow reorder and to enable/disable content bank content types and jump to settings
8568   *
8569   * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
8570   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8571   */
8572  class admin_setting_managecontentbankcontenttypes extends admin_setting {
8573  
8574      /**
8575       * Calls parent::__construct with specific arguments
8576       */
8577      public function __construct() {
8578          $this->nosave = true;
8579          parent::__construct('contentbank', new lang_string('managecontentbanktypes'), '', '');
8580      }
8581  
8582      /**
8583       * Always returns true
8584       *
8585       * @return true
8586       */
8587      public function get_setting() {
8588          return true;
8589      }
8590  
8591      /**
8592       * Always returns true
8593       *
8594       * @return true
8595       */
8596      public function get_defaultsetting() {
8597          return true;
8598      }
8599  
8600      /**
8601       * Always returns '' and doesn't write anything
8602       *
8603       * @param mixed $data string or array, must not be NULL
8604       * @return string Always returns ''
8605       */
8606      public function write_setting($data) {
8607          // Do not write any setting.
8608          return '';
8609      }
8610  
8611      /**
8612       * Search to find if Query is related to content bank plugin
8613       *
8614       * @param string $query The string to search for
8615       * @return bool true for related false for not
8616       */
8617      public function is_related($query) {
8618          if (parent::is_related($query)) {
8619              return true;
8620          }
8621          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8622          foreach ($types as $type) {
8623              if (strpos($type->component, $query) !== false ||
8624                  strpos(core_text::strtolower($type->displayname), $query) !== false) {
8625                  return true;
8626              }
8627          }
8628          return false;
8629      }
8630  
8631      /**
8632       * Return XHTML to display control
8633       *
8634       * @param mixed $data Unused
8635       * @param string $query
8636       * @return string highlight
8637       */
8638      public function output_html($data, $query='') {
8639          global $CFG, $OUTPUT;
8640          $return = '';
8641  
8642          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8643          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'order', 'up', 'down', 'default'));
8644          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
8645  
8646          $table = new html_table();
8647          $table->head  = array($txt->name, $txt->enable, $txt->order, $txt->settings, $txt->uninstall);
8648          $table->align = array('left', 'center', 'center', 'center', 'center');
8649          $table->attributes['class'] = 'managecontentbanktable generaltable admintable';
8650          $table->data  = array();
8651          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8652  
8653          $totalenabled = 0;
8654          $count = 0;
8655          foreach ($types as $type) {
8656              if ($type->is_enabled() && $type->is_installed_and_upgraded()) {
8657                  $totalenabled++;
8658              }
8659          }
8660  
8661          foreach ($types as $type) {
8662              $url = new moodle_url('/admin/contentbank.php',
8663                  array('sesskey' => sesskey(), 'name' => $type->name));
8664  
8665              $class = '';
8666              $strtypename = $type->displayname;
8667              if ($type->is_enabled()) {
8668                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
8669                      $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
8670              } else {
8671                  $class = 'dimmed_text';
8672                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
8673                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
8674              }
8675  
8676              $updown = '';
8677              if ($count) {
8678                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
8679                          $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
8680              } else {
8681                  $updown .= $spacer;
8682              }
8683              if ($count < count($types) - 1) {
8684                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
8685                          $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
8686              } else {
8687                  $updown .= $spacer;
8688              }
8689  
8690              $settings = '';
8691              if ($type->get_settings_url()) {
8692                  $settings = html_writer::link($type->get_settings_url(), $txt->settings);
8693              }
8694  
8695              $uninstall = '';
8696              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('contenttype_'.$type->name, 'manage')) {
8697                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
8698              }
8699  
8700              $row = new html_table_row(array($strtypename, $hideshow, $updown, $settings, $uninstall));
8701              if ($class) {
8702                  $row->attributes['class'] = $class;
8703              }
8704              $table->data[] = $row;
8705              $count++;
8706          }
8707          $return .= html_writer::table($table);
8708          return highlight($query, $return);
8709      }
8710  }
8711  
8712  /**
8713   * Initialise admin page - this function does require login and permission
8714   * checks specified in page definition.
8715   *
8716   * This function must be called on each admin page before other code.
8717   *
8718   * @global moodle_page $PAGE
8719   *
8720   * @param string $section name of page
8721   * @param string $extrabutton extra HTML that is added after the blocks editing on/off button.
8722   * @param array $extraurlparams an array paramname => paramvalue, or parameters that need to be
8723   *      added to the turn blocks editing on/off form, so this page reloads correctly.
8724   * @param string $actualurl if the actual page being viewed is not the normal one for this
8725   *      page (e.g. admin/roles/allow.php, instead of admin/roles/manage.php, you can pass the alternate URL here.
8726   * @param array $options Additional options that can be specified for page setup.
8727   *      pagelayout - This option can be used to set a specific pagelyaout, admin is default.
8728   */
8729  function admin_externalpage_setup($section, $extrabutton = '', array $extraurlparams = null, $actualurl = '', array $options = array()) {
8730      global $CFG, $PAGE, $USER, $SITE, $OUTPUT;
8731  
8732      $PAGE->set_context(null); // hack - set context to something, by default to system context
8733  
8734      $site = get_site();
8735      require_login(null, false);
8736  
8737      if (!empty($options['pagelayout'])) {
8738          // A specific page layout has been requested.
8739          $PAGE->set_pagelayout($options['pagelayout']);
8740      } else if ($section === 'upgradesettings') {
8741          $PAGE->set_pagelayout('maintenance');
8742      } else {
8743          $PAGE->set_pagelayout('admin');
8744      }
8745  
8746      $adminroot = admin_get_root(false, false); // settings not required for external pages
8747      $extpage = $adminroot->locate($section, true);
8748  
8749      if (empty($extpage) or !($extpage instanceof admin_externalpage)) {
8750          // The requested section isn't in the admin tree
8751          // It could be because the user has inadequate capapbilities or because the section doesn't exist
8752          if (!has_capability('moodle/site:config', context_system::instance())) {
8753              // The requested section could depend on a different capability
8754              // but most likely the user has inadequate capabilities
8755              print_error('accessdenied', 'admin');
8756          } else {
8757              print_error('sectionerror', 'admin', "$CFG->wwwroot/$CFG->admin/");
8758          }
8759      }
8760  
8761      // this eliminates our need to authenticate on the actual pages
8762      if (!$extpage->check_access()) {
8763          print_error('accessdenied', 'admin');
8764          die;
8765      }
8766  
8767      navigation_node::require_admin_tree();
8768  
8769      // $PAGE->set_extra_button($extrabutton); TODO
8770  
8771      if (!$actualurl) {
8772          $actualurl = $extpage->url;
8773      }
8774  
8775      $PAGE->set_url($actualurl, $extraurlparams);
8776      if (strpos($PAGE->pagetype, 'admin-') !== 0) {
8777          $PAGE->set_pagetype('admin-' . $PAGE->pagetype);
8778      }
8779  
8780      if (empty($SITE->fullname) || empty($SITE->shortname)) {
8781          // During initial install.
8782          $strinstallation = get_string('installation', 'install');
8783          $strsettings = get_string('settings');
8784          $PAGE->navbar->add($strsettings);
8785          $PAGE->set_title($strinstallation);
8786          $PAGE->set_heading($strinstallation);
8787          $PAGE->set_cacheable(false);
8788          return;
8789      }
8790  
8791      // Locate the current item on the navigation and make it active when found.
8792      $path = $extpage->path;
8793      $node = $PAGE->settingsnav;
8794      while ($node && count($path) > 0) {
8795          $node = $node->get(array_pop($path));
8796      }
8797      if ($node) {
8798          $node->make_active();
8799      }
8800  
8801      // Normal case.
8802      $adminediting = optional_param('adminedit', -1, PARAM_BOOL);
8803      if ($PAGE->user_allowed_editing() && $adminediting != -1) {
8804          $USER->editing = $adminediting;
8805      }
8806  
8807      $visiblepathtosection = array_reverse($extpage->visiblepath);
8808  
8809      if ($PAGE->user_allowed_editing()) {
8810          if ($PAGE->user_is_editing()) {
8811              $caption = get_string('blockseditoff');
8812              $url = new moodle_url($PAGE->url, array('adminedit'=>'0', 'sesskey'=>sesskey()));
8813          } else {
8814              $caption = get_string('blocksediton');
8815              $url = new moodle_url($PAGE->url, array('adminedit'=>'1', 'sesskey'=>sesskey()));
8816          }
8817          $PAGE->set_button($OUTPUT->single_button($url, $caption, 'get'));
8818      }
8819  
8820      $PAGE->set_title("$SITE->shortname: " . implode(": ", $visiblepathtosection));
8821      $PAGE->set_heading($SITE->fullname);
8822  
8823      // prevent caching in nav block
8824      $PAGE->navigation->clear_cache();
8825  }
8826  
8827  /**
8828   * Returns the reference to admin tree root
8829   *
8830   * @return object admin_root object
8831   */
8832  function admin_get_root($reload=false, $requirefulltree=true) {
8833      global $CFG, $DB, $OUTPUT, $ADMIN;
8834  
8835      if (is_null($ADMIN)) {
8836      // create the admin tree!
8837          $ADMIN = new admin_root($requirefulltree);
8838      }
8839  
8840      if ($reload or ($requirefulltree and !$ADMIN->fulltree)) {
8841          $ADMIN->purge_children($requirefulltree);
8842      }
8843  
8844      if (!$ADMIN->loaded) {
8845      // we process this file first to create categories first and in correct order
8846          require($CFG->dirroot.'/'.$CFG->admin.'/settings/top.php');
8847  
8848          // now we process all other files in admin/settings to build the admin tree
8849          foreach (glob($CFG->dirroot.'/'.$CFG->admin.'/settings/*.php') as $file) {
8850              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/top.php') {
8851                  continue;
8852              }
8853              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php') {
8854              // plugins are loaded last - they may insert pages anywhere
8855                  continue;
8856              }
8857              require($file);
8858          }
8859          require($CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php');
8860  
8861          $ADMIN->loaded = true;
8862      }
8863  
8864      return $ADMIN;
8865  }
8866  
8867  /// settings utility functions
8868  
8869  /**
8870   * This function applies default settings.
8871   * Because setting the defaults of some settings can enable other settings,
8872   * this function is called recursively until no more new settings are found.
8873   *
8874   * @param object $node, NULL means complete tree, null by default
8875   * @param bool $unconditional if true overrides all values with defaults, true by default
8876   * @param array $admindefaultsettings default admin settings to apply. Used recursively
8877   * @param array $settingsoutput The names and values of the changed settings. Used recursively
8878   * @return array $settingsoutput The names and values of the changed settings
8879   */
8880  function admin_apply_default_settings($node=null, $unconditional=true, $admindefaultsettings=array(), $settingsoutput=array()) {
8881      $counter = 0;
8882  
8883      if (is_null($node)) {
8884          core_plugin_manager::reset_caches();
8885          $node = admin_get_root(true, true);
8886          $counter = count($settingsoutput);
8887      }
8888  
8889      if ($node instanceof admin_category) {
8890          $entries = array_keys($node->children);
8891          foreach ($entries as $entry) {
8892              $settingsoutput = admin_apply_default_settings(
8893                      $node->children[$entry], $unconditional, $admindefaultsettings, $settingsoutput
8894                      );
8895          }
8896  
8897      } else if ($node instanceof admin_settingpage) {
8898          foreach ($node->settings as $setting) {
8899              if (!$unconditional && !is_null($setting->get_setting())) {
8900                  // Do not override existing defaults.
8901                  continue;
8902              }
8903              $defaultsetting = $setting->get_defaultsetting();
8904              if (is_null($defaultsetting)) {
8905                  // No value yet - default maybe applied after admin user creation or in upgradesettings.
8906                  continue;
8907              }
8908  
8909              $settingname = $node->name . '_' . $setting->name; // Get a unique name for the setting.
8910  
8911              if (!array_key_exists($settingname, $admindefaultsettings)) {  // Only update a setting if not already processed.
8912                  $admindefaultsettings[$settingname] = $settingname;
8913                  $settingsoutput[$settingname] = $defaultsetting;
8914  
8915                  // Set the default for this setting.
8916                  $setting->write_setting($defaultsetting);
8917                  $setting->write_setting_flags(null);
8918              } else {
8919                  unset($admindefaultsettings[$settingname]); // Remove processed settings.
8920              }
8921          }
8922      }
8923  
8924      // Call this function recursively until all settings are processed.
8925      if (($node instanceof admin_root) && ($counter != count($settingsoutput))) {
8926          $settingsoutput = admin_apply_default_settings(null, $unconditional, $admindefaultsettings, $settingsoutput);
8927      }
8928      // Just in case somebody modifies the list of active plugins directly.
8929      core_plugin_manager::reset_caches();
8930  
8931      return $settingsoutput;
8932  }
8933  
8934  /**
8935   * Store changed settings, this function updates the errors variable in $ADMIN
8936   *
8937   * @param object $formdata from form
8938   * @return int number of changed settings
8939   */
8940  function admin_write_settings($formdata) {
8941      global $CFG, $SITE, $DB;
8942  
8943      $olddbsessions = !empty($CFG->dbsessions);
8944      $formdata = (array)$formdata;
8945  
8946      $data = array();
8947      foreach ($formdata as $fullname=>$value) {
8948          if (strpos($fullname, 's_') !== 0) {
8949              continue; // not a config value
8950          }
8951          $data[$fullname] = $value;
8952      }
8953  
8954      $adminroot = admin_get_root();
8955      $settings = admin_find_write_settings($adminroot, $data);
8956  
8957      $count = 0;
8958      foreach ($settings as $fullname=>$setting) {
8959          /** @var $setting admin_setting */
8960          $original = $setting->get_setting();
8961          $error = $setting->write_setting($data[$fullname]);
8962          if ($error !== '') {
8963              $adminroot->errors[$fullname] = new stdClass();
8964              $adminroot->errors[$fullname]->data  = $data[$fullname];
8965              $adminroot->errors[$fullname]->id    = $setting->get_id();
8966              $adminroot->errors[$fullname]->error = $error;
8967          } else {
8968              $setting->write_setting_flags($data);
8969          }
8970          if ($setting->post_write_settings($original)) {
8971              $count++;
8972          }
8973      }
8974  
8975      if ($olddbsessions != !empty($CFG->dbsessions)) {
8976          require_logout();
8977      }
8978  
8979      // Now update $SITE - just update the fields, in case other people have a
8980      // a reference to it (e.g. $PAGE, $COURSE).
8981      $newsite = $DB->get_record('course', array('id'=>$SITE->id));
8982      foreach (get_object_vars($newsite) as $field => $value) {
8983          $SITE->$field = $value;
8984      }
8985  
8986      // now reload all settings - some of them might depend on the changed
8987      admin_get_root(true);
8988      return $count;
8989  }
8990  
8991  /**
8992   * Internal recursive function - finds all settings from submitted form
8993   *
8994   * @param object $node Instance of admin_category, or admin_settingpage
8995   * @param array $data
8996   * @return array
8997   */
8998  function admin_find_write_settings($node, $data) {
8999      $return = array();
9000  
9001      if (empty($data)) {
9002          return $return;
9003      }
9004  
9005      if ($node instanceof admin_category) {
9006          if ($node->check_access()) {
9007              $entries = array_keys($node->children);
9008              foreach ($entries as $entry) {
9009                  $return = array_merge($return, admin_find_write_settings($node->children[$entry], $data));
9010              }
9011          }
9012  
9013      } else if ($node instanceof admin_settingpage) {
9014          if ($node->check_access()) {
9015              foreach ($node->settings as $setting) {
9016                  $fullname = $setting->get_full_name();
9017                  if (array_key_exists($fullname, $data)) {
9018                      $return[$fullname] = $setting;
9019                  }
9020              }
9021          }
9022  
9023      }
9024  
9025      return $return;
9026  }
9027  
9028  /**
9029   * Internal function - prints the search results
9030   *
9031   * @param string $query String to search for
9032   * @return string empty or XHTML
9033   */
9034  function admin_search_settings_html($query) {
9035      global $CFG, $OUTPUT, $PAGE;
9036  
9037      if (core_text::strlen($query) < 2) {
9038          return '';
9039      }
9040      $query = core_text::strtolower($query);
9041  
9042      $adminroot = admin_get_root();
9043      $findings = $adminroot->search($query);
9044      $savebutton = false;
9045  
9046      $tpldata = (object) [
9047          'actionurl' => $PAGE->url->out(false),
9048          'results' => [],
9049          'sesskey' => sesskey(),
9050      ];
9051  
9052      foreach ($findings as $found) {
9053          $page     = $found->page;
9054          $settings = $found->settings;
9055          if ($page->is_hidden()) {
9056          // hidden pages are not displayed in search results
9057              continue;
9058          }
9059  
9060          $heading = highlight($query, $page->visiblename);
9061          $headingurl = null;
9062          if ($page instanceof admin_externalpage) {
9063              $headingurl = new moodle_url($page->url);
9064          } else if ($page instanceof admin_settingpage) {
9065              $headingurl = new moodle_url('/admin/settings.php', ['section' => $page->name]);
9066          } else {
9067              continue;
9068          }
9069  
9070          // Locate the page in the admin root and populate its visiblepath attribute.
9071          $path = array();
9072          $located = $adminroot->locate($page->name, true);
9073          if ($located) {
9074              foreach ($located->visiblepath as $pathitem) {
9075                  array_unshift($path, (string) $pathitem);
9076              }
9077          }
9078  
9079          $sectionsettings = [];
9080          if (!empty($settings)) {
9081              foreach ($settings as $setting) {
9082                  if (empty($setting->nosave)) {
9083                      $savebutton = true;
9084                  }
9085                  $fullname = $setting->get_full_name();
9086                  if (array_key_exists($fullname, $adminroot->errors)) {
9087                      $data = $adminroot->errors[$fullname]->data;
9088                  } else {
9089                      $data = $setting->get_setting();
9090                  // do not use defaults if settings not available - upgradesettings handles the defaults!
9091                  }
9092                  $sectionsettings[] = $setting->output_html($data, $query);
9093              }
9094          }
9095  
9096          $tpldata->results[] = (object) [
9097              'title' => $heading,
9098              'path' => $path,
9099              'url' => $headingurl->out(false),
9100              'settings' => $sectionsettings
9101          ];
9102      }
9103  
9104      $tpldata->showsave = $savebutton;
9105      $tpldata->hasresults = !empty($tpldata->results);
9106  
9107      return $OUTPUT->render_from_template('core_admin/settings_search_results', $tpldata);
9108  }
9109  
9110  /**
9111   * Internal function - returns arrays of html pages with uninitialised settings
9112   *
9113   * @param object $node Instance of admin_category or admin_settingpage
9114   * @return array
9115   */
9116  function admin_output_new_settings_by_page($node) {
9117      global $OUTPUT;
9118      $return = array();
9119  
9120      if ($node instanceof admin_category) {
9121          $entries = array_keys($node->children);
9122          foreach ($entries as $entry) {
9123              $return += admin_output_new_settings_by_page($node->children[$entry]);
9124          }
9125  
9126      } else if ($node instanceof admin_settingpage) {
9127              $newsettings = array();
9128              foreach ($node->settings as $setting) {
9129                  if (is_null($setting->get_setting())) {
9130                      $newsettings[] = $setting;
9131                  }
9132              }
9133              if (count($newsettings) > 0) {
9134                  $adminroot = admin_get_root();
9135                  $page = $OUTPUT->heading(get_string('upgradesettings','admin').' - '.$node->visiblename, 2, 'main');
9136                  $page .= '<fieldset class="adminsettings">'."\n";
9137                  foreach ($newsettings as $setting) {
9138                      $fullname = $setting->get_full_name();
9139                      if (array_key_exists($fullname, $adminroot->errors)) {
9140                          $data = $adminroot->errors[$fullname]->data;
9141                      } else {
9142                          $data = $setting->get_setting();
9143                          if (is_null($data)) {
9144                              $data = $setting->get_defaultsetting();
9145                          }
9146                      }
9147                      $page .= '<div class="clearer"><!-- --></div>'."\n";
9148                      $page .= $setting->output_html($data);
9149                  }
9150                  $page .= '</fieldset>';
9151                  $return[$node->name] = $page;
9152              }
9153          }
9154  
9155      return $return;
9156  }
9157  
9158  /**
9159   * Format admin settings
9160   *
9161   * @param object $setting
9162   * @param string $title label element
9163   * @param string $form form fragment, html code - not highlighted automatically
9164   * @param string $description
9165   * @param mixed $label link label to id, true by default or string being the label to connect it to
9166   * @param string $warning warning text
9167   * @param sting $defaultinfo defaults info, null means nothing, '' is converted to "Empty" string, defaults to null
9168   * @param string $query search query to be highlighted
9169   * @return string XHTML
9170   */
9171  function format_admin_setting($setting, $title='', $form='', $description='', $label=true, $warning='', $defaultinfo=NULL, $query='') {
9172      global $CFG, $OUTPUT;
9173  
9174      $context = (object) [
9175          'name' => empty($setting->plugin) ? $setting->name : "$setting->plugin | $setting->name",
9176          'fullname' => $setting->get_full_name(),
9177      ];
9178  
9179      // Sometimes the id is not id_s_name, but id_s_name_m or something, and this does not validate.
9180      if ($label === true) {
9181          $context->labelfor = $setting->get_id();
9182      } else if ($label === false) {
9183          $context->labelfor = '';
9184      } else {
9185          $context->labelfor = $label;
9186      }
9187  
9188      $form .= $setting->output_setting_flags();
9189  
9190      $context->warning = $warning;
9191      $context->override = '';
9192      if (empty($setting->plugin)) {
9193          if (array_key_exists($setting->name, $CFG->config_php_settings)) {
9194              $context->override = get_string('configoverride', 'admin');
9195          }
9196      } else {
9197          if (array_key_exists($setting->plugin, $CFG->forced_plugin_settings) and array_key_exists($setting->name, $CFG->forced_plugin_settings[$setting->plugin])) {
9198              $context->override = get_string('configoverride', 'admin');
9199          }
9200      }
9201  
9202      $defaults = array();
9203      if (!is_null($defaultinfo)) {
9204          if ($defaultinfo === '') {
9205              $defaultinfo = get_string('emptysettingvalue', 'admin');
9206          }
9207          $defaults[] = $defaultinfo;
9208      }
9209  
9210      $context->default = null;
9211      $setting->get_setting_flag_defaults($defaults);
9212      if (!empty($defaults)) {
9213          $defaultinfo = implode(', ', $defaults);
9214          $defaultinfo = highlight($query, nl2br(s($defaultinfo)));
9215          $context->default = get_string('defaultsettinginfo', 'admin', $defaultinfo);
9216      }
9217  
9218  
9219      $context->error = '';
9220      $adminroot = admin_get_root();
9221      if (array_key_exists($context->fullname, $adminroot->errors)) {
9222          $context->error = $adminroot->errors[$context->fullname]->error;
9223      }
9224  
9225      if ($dependenton = $setting->get_dependent_on()) {
9226          $context->dependenton = get_string('settingdependenton', 'admin', implode(', ', $dependenton));
9227      }
9228  
9229      $context->id = 'admin-' . $setting->name;
9230      $context->title = highlightfast($query, $title);
9231      $context->name = highlightfast($query, $context->name);
9232      $context->description = highlight($query, markdown_to_html($description));
9233      $context->element = $form;
9234      $context->forceltr = $setting->get_force_ltr();
9235      $context->customcontrol = $setting->has_custom_form_control();
9236  
9237      return $OUTPUT->render_from_template('core_admin/setting', $context);
9238  }
9239  
9240  /**
9241   * Based on find_new_settings{@link ()}  in upgradesettings.php
9242   * Looks to find any admin settings that have not been initialized. Returns 1 if it finds any.
9243   *
9244   * @param object $node Instance of admin_category, or admin_settingpage
9245   * @return boolean true if any settings haven't been initialised, false if they all have
9246   */
9247  function any_new_admin_settings($node) {
9248  
9249      if ($node instanceof admin_category) {
9250          $entries = array_keys($node->children);
9251          foreach ($entries as $entry) {
9252              if (any_new_admin_settings($node->children[$entry])) {
9253                  return true;
9254              }
9255          }
9256  
9257      } else if ($node instanceof admin_settingpage) {
9258              foreach ($node->settings as $setting) {
9259                  if ($setting->get_setting() === NULL) {
9260                      return true;
9261                  }
9262              }
9263          }
9264  
9265      return false;
9266  }
9267  
9268  /**
9269   * Given a table and optionally a column name should replaces be done?
9270   *
9271   * @param string $table name
9272   * @param string $column name
9273   * @return bool success or fail
9274   */
9275  function db_should_replace($table, $column = '', $additionalskiptables = ''): bool {
9276  
9277      // TODO: this is horrible hack, we should have a hook and each plugin should be responsible for proper replacing...
9278      $skiptables = ['config', 'config_plugins', 'filter_config', 'sessions',
9279          'events_queue', 'repository_instance_config', 'block_instances', 'files'];
9280  
9281      // Additional skip tables.
9282      if (!empty($additionalskiptables)) {
9283          $skiptables = array_merge($skiptables, explode(',', str_replace(' ', '',  $additionalskiptables)));
9284      }
9285  
9286      // Don't process these.
9287      if (in_array($table, $skiptables)) {
9288          return false;
9289      }
9290  
9291      // To be safe never replace inside a table that looks related to logging.
9292      if (preg_match('/(^|_)logs?($|_)/', $table)) {
9293          return false;
9294      }
9295  
9296      // Do column based exclusions.
9297      if (!empty($column)) {
9298          // Don't touch anything that looks like a hash.
9299          if (preg_match('/hash$/', $column)) {
9300              return false;
9301          }
9302      }
9303  
9304      return true;
9305  }
9306  
9307  /**
9308   * Moved from admin/replace.php so that we can use this in cron
9309   *
9310   * @param string $search string to look for
9311   * @param string $replace string to replace
9312   * @return bool success or fail
9313   */
9314  function db_replace($search, $replace, $additionalskiptables = '') {
9315      global $DB, $CFG, $OUTPUT;
9316  
9317      // Turn off time limits, sometimes upgrades can be slow.
9318      core_php_time_limit::raise();
9319  
9320      if (!$tables = $DB->get_tables() ) {    // No tables yet at all.
9321          return false;
9322      }
9323      foreach ($tables as $table) {
9324  
9325          if (!db_should_replace($table, '', $additionalskiptables)) {
9326              continue;
9327          }
9328  
9329          if ($columns = $DB->get_columns($table)) {
9330              $DB->set_debug(true);
9331              foreach ($columns as $column) {
9332                  if (!db_should_replace($table, $column->name)) {
9333                      continue;
9334                  }
9335                  $DB->replace_all_text($table, $column, $search, $replace);
9336              }
9337              $DB->set_debug(false);
9338          }
9339      }
9340  
9341      // delete modinfo caches
9342      rebuild_course_cache(0, true);
9343  
9344      // TODO: we should ask all plugins to do the search&replace, for now let's do only blocks...
9345      $blocks = core_component::get_plugin_list('block');
9346      foreach ($blocks as $blockname=>$fullblock) {
9347          if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
9348              continue;
9349          }
9350  
9351          if (!is_readable($fullblock.'/lib.php')) {
9352              continue;
9353          }
9354  
9355          $function = 'block_'.$blockname.'_global_db_replace';
9356          include_once($fullblock.'/lib.php');
9357          if (!function_exists($function)) {
9358              continue;
9359          }
9360  
9361          echo $OUTPUT->notification("Replacing in $blockname blocks...", 'notifysuccess');
9362          $function($search, $replace);
9363          echo $OUTPUT->notification("...finished", 'notifysuccess');
9364      }
9365  
9366      // Trigger an event.
9367      $eventargs = [
9368          'context' => context_system::instance(),
9369          'other' => [
9370              'search' => $search,
9371              'replace' => $replace
9372          ]
9373      ];
9374      $event = \core\event\database_text_field_content_replaced::create($eventargs);
9375      $event->trigger();
9376  
9377      purge_all_caches();
9378  
9379      return true;
9380  }
9381  
9382  /**
9383   * Manage repository settings
9384   *
9385   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
9386   */
9387  class admin_setting_managerepository extends admin_setting {
9388  /** @var string */
9389      private $baseurl;
9390  
9391      /**
9392       * calls parent::__construct with specific arguments
9393       */
9394      public function __construct() {
9395          global $CFG;
9396          parent::__construct('managerepository', get_string('manage', 'repository'), '', '');
9397          $this->baseurl = $CFG->wwwroot . '/' . $CFG->admin . '/repository.php?sesskey=' . sesskey();
9398      }
9399  
9400      /**
9401       * Always returns true, does nothing
9402       *
9403       * @return true
9404       */
9405      public function get_setting() {
9406          return true;
9407      }
9408  
9409      /**
9410       * Always returns true does nothing
9411       *
9412       * @return true
9413       */
9414      public function get_defaultsetting() {
9415          return true;
9416      }
9417  
9418      /**
9419       * Always returns s_managerepository
9420       *
9421       * @return string Always return 's_managerepository'
9422       */
9423      public function get_full_name() {
9424          return 's_managerepository';
9425      }
9426  
9427      /**
9428       * Always returns '' doesn't do anything
9429       */
9430      public function write_setting($data) {
9431          $url = $this->baseurl . '&amp;new=' . $data;
9432          return '';
9433      // TODO
9434      // Should not use redirect and exit here
9435      // Find a better way to do this.
9436      // redirect($url);
9437      // exit;
9438      }
9439  
9440      /**
9441       * Searches repository plugins for one that matches $query
9442       *
9443       * @param string $query The string to search for
9444       * @return bool true if found, false if not
9445       */
9446      public function is_related($query) {
9447          if (parent::is_related($query)) {
9448              return true;
9449          }
9450  
9451          $repositories= core_component::get_plugin_list('repository');
9452          foreach ($repositories as $p => $dir) {
9453              if (strpos($p, $query) !== false) {
9454                  return true;
9455              }
9456          }
9457          foreach (repository::get_types() as $instance) {
9458              $title = $instance->get_typename();
9459              if (strpos(core_text::strtolower($title), $query) !== false) {
9460                  return true;
9461              }
9462          }
9463          return false;
9464      }
9465  
9466      /**
9467       * Helper function that generates a moodle_url object
9468       * relevant to the repository
9469       */
9470  
9471      function repository_action_url($repository) {
9472          return new moodle_url($this->baseurl, array('sesskey'=>sesskey(), 'repos'=>$repository));
9473      }
9474  
9475      /**
9476       * Builds XHTML to display the control
9477       *
9478       * @param string $data Unused
9479       * @param string $query
9480       * @return string XHTML
9481       */
9482      public function output_html($data, $query='') {
9483          global $CFG, $USER, $OUTPUT;
9484  
9485          // Get strings that are used
9486          $strshow = get_string('on', 'repository');
9487          $strhide = get_string('off', 'repository');
9488          $strdelete = get_string('disabled', 'repository');
9489  
9490          $actionchoicesforexisting = array(
9491              'show' => $strshow,
9492              'hide' => $strhide,
9493              'delete' => $strdelete
9494          );
9495  
9496          $actionchoicesfornew = array(
9497              'newon' => $strshow,
9498              'newoff' => $strhide,
9499              'delete' => $strdelete
9500          );
9501  
9502          $return = '';
9503          $return .= $OUTPUT->box_start('generalbox');
9504  
9505          // Set strings that are used multiple times
9506          $settingsstr = get_string('settings');
9507          $disablestr = get_string('disable');
9508  
9509          // Table to list plug-ins
9510          $table = new html_table();
9511          $table->head = array(get_string('name'), get_string('isactive', 'repository'), get_string('order'), $settingsstr);
9512          $table->align = array('left', 'center', 'center', 'center', 'center');
9513          $table->data = array();
9514  
9515          // Get list of used plug-ins
9516          $repositorytypes = repository::get_types();
9517          if (!empty($repositorytypes)) {
9518              // Array to store plugins being used
9519              $alreadyplugins = array();
9520              $totalrepositorytypes = count($repositorytypes);
9521              $updowncount = 1;
9522              foreach ($repositorytypes as $i) {
9523                  $settings = '';
9524                  $typename = $i->get_typename();
9525                  // Display edit link only if you can config the type or if it has multiple instances (e.g. has instance config)
9526                  $typeoptionnames = repository::static_function($typename, 'get_type_option_names');
9527                  $instanceoptionnames = repository::static_function($typename, 'get_instance_option_names');
9528  
9529                  if (!empty($typeoptionnames) || !empty($instanceoptionnames)) {
9530                      // Calculate number of instances in order to display them for the Moodle administrator
9531                      if (!empty($instanceoptionnames)) {
9532                          $params = array();
9533                          $params['context'] = array(context_system::instance());
9534                          $params['onlyvisible'] = false;
9535                          $params['type'] = $typename;
9536                          $admininstancenumber = count(repository::static_function($typename, 'get_instances', $params));
9537                          // site instances
9538                          $admininstancenumbertext = get_string('instancesforsite', 'repository', $admininstancenumber);
9539                          $params['context'] = array();
9540                          $instances = repository::static_function($typename, 'get_instances', $params);
9541                          $courseinstances = array();
9542                          $userinstances = array();
9543  
9544                          foreach ($instances as $instance) {
9545                              $repocontext = context::instance_by_id($instance->instance->contextid);
9546                              if ($repocontext->contextlevel == CONTEXT_COURSE) {
9547                                  $courseinstances[] = $instance;
9548                              } else if ($repocontext->contextlevel == CONTEXT_USER) {
9549                                  $userinstances[] = $instance;
9550                              }
9551                          }
9552                          // course instances
9553                          $instancenumber = count($courseinstances);
9554                          $courseinstancenumbertext = get_string('instancesforcourses', 'repository', $instancenumber);
9555  
9556                          // user private instances
9557                          $instancenumber =  count($userinstances);
9558                          $userinstancenumbertext = get_string('instancesforusers', 'repository', $instancenumber);
9559                      } else {
9560                          $admininstancenumbertext = "";
9561                          $courseinstancenumbertext = "";
9562                          $userinstancenumbertext = "";
9563                      }
9564  
9565                      $settings .= '<a href="' . $this->baseurl . '&amp;action=edit&amp;repos=' . $typename . '">' . $settingsstr .'</a>';
9566  
9567                      $settings .= $OUTPUT->container_start('mdl-left');
9568                      $settings .= '<br/>';
9569                      $settings .= $admininstancenumbertext;
9570                      $settings .= '<br/>';
9571                      $settings .= $courseinstancenumbertext;
9572                      $settings .= '<br/>';
9573                      $settings .= $userinstancenumbertext;
9574                      $settings .= $OUTPUT->container_end();
9575                  }
9576                  // Get the current visibility
9577                  if ($i->get_visible()) {
9578                      $currentaction = 'show';
9579                  } else {
9580                      $currentaction = 'hide';
9581                  }
9582  
9583                  $select = new single_select($this->repository_action_url($typename, 'repos'), 'action', $actionchoicesforexisting, $currentaction, null, 'applyto' . basename($typename));
9584  
9585                  // Display up/down link
9586                  $updown = '';
9587                  // Should be done with CSS instead.
9588                  $spacer = $OUTPUT->spacer(array('height' => 15, 'width' => 15, 'class' => 'smallicon'));
9589  
9590                  if ($updowncount > 1) {
9591                      $updown .= "<a href=\"$this->baseurl&amp;action=moveup&amp;repos=".$typename."\">";
9592                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
9593                  }
9594                  else {
9595                      $updown .= $spacer;
9596                  }
9597                  if ($updowncount < $totalrepositorytypes) {
9598                      $updown .= "<a href=\"$this->baseurl&amp;action=movedown&amp;repos=".$typename."\">";
9599                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
9600                  }
9601                  else {
9602                      $updown .= $spacer;
9603                  }
9604  
9605                  $updowncount++;
9606  
9607                  $table->data[] = array($i->get_readablename(), $OUTPUT->render($select), $updown, $settings);
9608  
9609                  if (!in_array($typename, $alreadyplugins)) {
9610                      $alreadyplugins[] = $typename;
9611                  }
9612              }
9613          }
9614  
9615          // Get all the plugins that exist on disk
9616          $plugins = core_component::get_plugin_list('repository');
9617          if (!empty($plugins)) {
9618              foreach ($plugins as $plugin => $dir) {
9619                  // Check that it has not already been listed
9620                  if (!in_array($plugin, $alreadyplugins)) {
9621                      $select = new single_select($this->repository_action_url($plugin, 'repos'), 'action', $actionchoicesfornew, 'delete', null, 'applyto' . basename($plugin));
9622                      $table->data[] = array(get_string('pluginname', 'repository_'.$plugin), $OUTPUT->render($select), '', '');
9623                  }
9624              }
9625          }
9626  
9627          $return .= html_writer::table($table);
9628          $return .= $OUTPUT->box_end();
9629          return highlight($query, $return);
9630      }
9631  }
9632  
9633  /**
9634   * Special checkbox for enable mobile web service
9635   * If enable then we store the service id of the mobile service into config table
9636   * If disable then we unstore the service id from the config table
9637   */
9638  class admin_setting_enablemobileservice extends admin_setting_configcheckbox {
9639  
9640      /** @var boolean True means that the capability 'webservice/rest:use' is set for authenticated user role */
9641      private $restuse;
9642  
9643      /**
9644       * Return true if Authenticated user role has the capability 'webservice/rest:use', otherwise false.
9645       *
9646       * @return boolean
9647       */
9648      private function is_protocol_cap_allowed() {
9649          global $DB, $CFG;
9650  
9651          // If the $this->restuse variable is not set, it needs to be set.
9652          if (empty($this->restuse) and $this->restuse!==false) {
9653              $params = array();
9654              $params['permission'] = CAP_ALLOW;
9655              $params['roleid'] = $CFG->defaultuserroleid;
9656              $params['capability'] = 'webservice/rest:use';
9657              $this->restuse = $DB->record_exists('role_capabilities', $params);
9658          }
9659  
9660          return $this->restuse;
9661      }
9662  
9663      /**
9664       * Set the 'webservice/rest:use' to the Authenticated user role (allow or not)
9665       * @param type $status true to allow, false to not set
9666       */
9667      private function set_protocol_cap($status) {
9668          global $CFG;
9669          if ($status and !$this->is_protocol_cap_allowed()) {
9670              //need to allow the cap
9671              $permission = CAP_ALLOW;
9672              $assign = true;
9673          } else if (!$status and $this->is_protocol_cap_allowed()){
9674              //need to disallow the cap
9675              $permission = CAP_INHERIT;
9676              $assign = true;
9677          }
9678          if (!empty($assign)) {
9679              $systemcontext = context_system::instance();
9680              assign_capability('webservice/rest:use', $permission, $CFG->defaultuserroleid, $systemcontext->id, true);
9681          }
9682      }
9683  
9684      /**
9685       * Builds XHTML to display the control.
9686       * The main purpose of this overloading is to display a warning when https
9687       * is not supported by the server
9688       * @param string $data Unused
9689       * @param string $query
9690       * @return string XHTML
9691       */
9692      public function output_html($data, $query='') {
9693          global $OUTPUT;
9694          $html = parent::output_html($data, $query);
9695  
9696          if ((string)$data === $this->yes) {
9697              $notifications = tool_mobile\api::get_potential_config_issues(); // Safe to call, plugin available if we reach here.
9698              foreach ($notifications as $notification) {
9699                  $message = get_string($notification[0], $notification[1]);
9700                  $html .= $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING);
9701              }
9702          }
9703  
9704          return $html;
9705      }
9706  
9707      /**
9708       * Retrieves the current setting using the objects name
9709       *
9710       * @return string
9711       */
9712      public function get_setting() {
9713          global $CFG;
9714  
9715          // First check if is not set.
9716          $result = $this->config_read($this->name);
9717          if (is_null($result)) {
9718              return null;
9719          }
9720  
9721          // For install cli script, $CFG->defaultuserroleid is not set so return 0
9722          // Or if web services aren't enabled this can't be,
9723          if (empty($CFG->defaultuserroleid) || empty($CFG->enablewebservices)) {
9724              return 0;
9725          }
9726  
9727          require_once($CFG->dirroot . '/webservice/lib.php');
9728          $webservicemanager = new webservice();
9729          $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9730          if ($mobileservice->enabled and $this->is_protocol_cap_allowed()) {
9731              return $result;
9732          } else {
9733              return 0;
9734          }
9735      }
9736  
9737      /**
9738       * Save the selected setting
9739       *
9740       * @param string $data The selected site
9741       * @return string empty string or error message
9742       */
9743      public function write_setting($data) {
9744          global $DB, $CFG;
9745  
9746          //for install cli script, $CFG->defaultuserroleid is not set so do nothing
9747          if (empty($CFG->defaultuserroleid)) {
9748              return '';
9749          }
9750  
9751          $servicename = MOODLE_OFFICIAL_MOBILE_SERVICE;
9752  
9753          require_once($CFG->dirroot . '/webservice/lib.php');
9754          $webservicemanager = new webservice();
9755  
9756          $updateprotocol = false;
9757          if ((string)$data === $this->yes) {
9758               //code run when enable mobile web service
9759               //enable web service systeme if necessary
9760               set_config('enablewebservices', true);
9761  
9762               //enable mobile service
9763               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9764               $mobileservice->enabled = 1;
9765               $webservicemanager->update_external_service($mobileservice);
9766  
9767               // Enable REST server.
9768               $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
9769  
9770               if (!in_array('rest', $activeprotocols)) {
9771                   $activeprotocols[] = 'rest';
9772                   $updateprotocol = true;
9773               }
9774  
9775               if ($updateprotocol) {
9776                   set_config('webserviceprotocols', implode(',', $activeprotocols));
9777               }
9778  
9779               // Allow rest:use capability for authenticated user.
9780               $this->set_protocol_cap(true);
9781           } else {
9782               // Disable the mobile service.
9783               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9784               $mobileservice->enabled = 0;
9785               $webservicemanager->update_external_service($mobileservice);
9786           }
9787  
9788          return (parent::write_setting($data));
9789      }
9790  }
9791  
9792  /**
9793   * Special class for management of external services
9794   *
9795   * @author Petr Skoda (skodak)
9796   */
9797  class admin_setting_manageexternalservices extends admin_setting {
9798      /**
9799       * Calls parent::__construct with specific arguments
9800       */
9801      public function __construct() {
9802          $this->nosave = true;
9803          parent::__construct('webservicesui', get_string('externalservices', 'webservice'), '', '');
9804      }
9805  
9806      /**
9807       * Always returns true, does nothing
9808       *
9809       * @return true
9810       */
9811      public function get_setting() {
9812          return true;
9813      }
9814  
9815      /**
9816       * Always returns true, does nothing
9817       *
9818       * @return true
9819       */
9820      public function get_defaultsetting() {
9821          return true;
9822      }
9823  
9824      /**
9825       * Always returns '', does not write anything
9826       *
9827       * @return string Always returns ''
9828       */
9829      public function write_setting($data) {
9830      // do not write any setting
9831          return '';
9832      }
9833  
9834      /**
9835       * Checks if $query is one of the available external services
9836       *
9837       * @param string $query The string to search for
9838       * @return bool Returns true if found, false if not
9839       */
9840      public function is_related($query) {
9841          global $DB;
9842  
9843          if (parent::is_related($query)) {
9844              return true;
9845          }
9846  
9847          $services = $DB->get_records('external_services', array(), 'id, name');
9848          foreach ($services as $service) {
9849              if (strpos(core_text::strtolower($service->name), $query) !== false) {
9850                  return true;
9851              }
9852          }
9853          return false;
9854      }
9855  
9856      /**
9857       * Builds the XHTML to display the control
9858       *
9859       * @param string $data Unused
9860       * @param string $query
9861       * @return string
9862       */
9863      public function output_html($data, $query='') {
9864          global $CFG, $OUTPUT, $DB;
9865  
9866          // display strings
9867          $stradministration = get_string('administration');
9868          $stredit = get_string('edit');
9869          $strservice = get_string('externalservice', 'webservice');
9870          $strdelete = get_string('delete');
9871          $strplugin = get_string('plugin', 'admin');
9872          $stradd = get_string('add');
9873          $strfunctions = get_string('functions', 'webservice');
9874          $strusers = get_string('users');
9875          $strserviceusers = get_string('serviceusers', 'webservice');
9876  
9877          $esurl = "$CFG->wwwroot/$CFG->admin/webservice/service.php";
9878          $efurl = "$CFG->wwwroot/$CFG->admin/webservice/service_functions.php";
9879          $euurl = "$CFG->wwwroot/$CFG->admin/webservice/service_users.php";
9880  
9881          // built in services
9882           $services = $DB->get_records_select('external_services', 'component IS NOT NULL', null, 'name');
9883           $return = "";
9884           if (!empty($services)) {
9885              $return .= $OUTPUT->heading(get_string('servicesbuiltin', 'webservice'), 3, 'main');
9886  
9887  
9888  
9889              $table = new html_table();
9890              $table->head  = array($strservice, $strplugin, $strfunctions, $strusers, $stredit);
9891              $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9892              $table->id = 'builtinservices';
9893              $table->attributes['class'] = 'admintable externalservices generaltable';
9894              $table->data  = array();
9895  
9896              // iterate through auth plugins and add to the display table
9897              foreach ($services as $service) {
9898                  $name = $service->name;
9899  
9900                  // hide/show link
9901                  if ($service->enabled) {
9902                      $displayname = "<span>$name</span>";
9903                  } else {
9904                      $displayname = "<span class=\"dimmed_text\">$name</span>";
9905                  }
9906  
9907                  $plugin = $service->component;
9908  
9909                  $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9910  
9911                  if ($service->restrictedusers) {
9912                      $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9913                  } else {
9914                      $users = get_string('allusers', 'webservice');
9915                  }
9916  
9917                  $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9918  
9919                  // add a row to the table
9920                  $table->data[] = array($displayname, $plugin, $functions, $users, $edit);
9921              }
9922              $return .= html_writer::table($table);
9923          }
9924  
9925          // Custom services
9926          $return .= $OUTPUT->heading(get_string('servicescustom', 'webservice'), 3, 'main');
9927          $services = $DB->get_records_select('external_services', 'component IS NULL', null, 'name');
9928  
9929          $table = new html_table();
9930          $table->head  = array($strservice, $strdelete, $strfunctions, $strusers, $stredit);
9931          $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9932          $table->id = 'customservices';
9933          $table->attributes['class'] = 'admintable externalservices generaltable';
9934          $table->data  = array();
9935  
9936          // iterate through auth plugins and add to the display table
9937          foreach ($services as $service) {
9938              $name = $service->name;
9939  
9940              // hide/show link
9941              if ($service->enabled) {
9942                  $displayname = "<span>$name</span>";
9943              } else {
9944                  $displayname = "<span class=\"dimmed_text\">$name</span>";
9945              }
9946  
9947              // delete link
9948              $delete = "<a href=\"$esurl?action=delete&amp;sesskey=".sesskey()."&amp;id=$service->id\">$strdelete</a>";
9949  
9950              $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9951  
9952              if ($service->restrictedusers) {
9953                  $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9954              } else {
9955                  $users = get_string('allusers', 'webservice');
9956              }
9957  
9958              $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9959  
9960              // add a row to the table
9961              $table->data[] = array($displayname, $delete, $functions, $users, $edit);
9962          }
9963          // add new custom service option
9964          $return .= html_writer::table($table);
9965  
9966          $return .= '<br />';
9967          // add a token to the table
9968          $return .= "<a href=\"$esurl?id=0\">$stradd</a>";
9969  
9970          return highlight($query, $return);
9971      }
9972  }
9973  
9974  /**
9975   * Special class for overview of external services
9976   *
9977   * @author Jerome Mouneyrac
9978   */
9979  class admin_setting_webservicesoverview extends admin_setting {
9980  
9981      /**
9982       * Calls parent::__construct with specific arguments
9983       */
9984      public function __construct() {
9985          $this->nosave = true;
9986          parent::__construct('webservicesoverviewui',
9987                          get_string('webservicesoverview', 'webservice'), '', '');
9988      }
9989  
9990      /**
9991       * Always returns true, does nothing
9992       *
9993       * @return true
9994       */
9995      public function get_setting() {
9996          return true;
9997      }
9998  
9999      /**
10000       * Always returns true, does nothing
10001       *
10002       * @return true
10003       */
10004      public function get_defaultsetting() {
10005          return true;
10006      }
10007  
10008      /**
10009       * Always returns '', does not write anything
10010       *
10011       * @return string Always returns ''
10012       */
10013      public function write_setting($data) {
10014          // do not write any setting
10015          return '';
10016      }
10017  
10018      /**
10019       * Builds the XHTML to display the control
10020       *
10021       * @param string $data Unused
10022       * @param string $query
10023       * @return string
10024       */
10025      public function output_html($data, $query='') {
10026          global $CFG, $OUTPUT;
10027  
10028          $return = "";
10029          $brtag = html_writer::empty_tag('br');
10030  
10031          /// One system controlling Moodle with Token
10032          $return .= $OUTPUT->heading(get_string('onesystemcontrolling', 'webservice'), 3, 'main');
10033          $table = new html_table();
10034          $table->head = array(get_string('step', 'webservice'), get_string('status'),
10035              get_string('description'));
10036          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
10037          $table->id = 'onesystemcontrol';
10038          $table->attributes['class'] = 'admintable wsoverview generaltable';
10039          $table->data = array();
10040  
10041          $return .= $brtag . get_string('onesystemcontrollingdescription', 'webservice')
10042                  . $brtag . $brtag;
10043  
10044          /// 1. Enable Web Services
10045          $row = array();
10046          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10047          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10048                          array('href' => $url));
10049          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10050          if ($CFG->enablewebservices) {
10051              $status = get_string('yes');
10052          }
10053          $row[1] = $status;
10054          $row[2] = get_string('enablewsdescription', 'webservice');
10055          $table->data[] = $row;
10056  
10057          /// 2. Enable protocols
10058          $row = array();
10059          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10060          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10061                          array('href' => $url));
10062          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10063          //retrieve activated protocol
10064          $active_protocols = empty($CFG->webserviceprotocols) ?
10065                  array() : explode(',', $CFG->webserviceprotocols);
10066          if (!empty($active_protocols)) {
10067              $status = "";
10068              foreach ($active_protocols as $protocol) {
10069                  $status .= $protocol . $brtag;
10070              }
10071          }
10072          $row[1] = $status;
10073          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10074          $table->data[] = $row;
10075  
10076          /// 3. Create user account
10077          $row = array();
10078          $url = new moodle_url("/user/editadvanced.php?id=-1");
10079          $row[0] = "3. " . html_writer::tag('a', get_string('createuser', 'webservice'),
10080                          array('href' => $url));
10081          $row[1] = "";
10082          $row[2] = get_string('createuserdescription', 'webservice');
10083          $table->data[] = $row;
10084  
10085          /// 4. Add capability to users
10086          $row = array();
10087          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10088          $row[0] = "4. " . html_writer::tag('a', get_string('checkusercapability', 'webservice'),
10089                          array('href' => $url));
10090          $row[1] = "";
10091          $row[2] = get_string('checkusercapabilitydescription', 'webservice');
10092          $table->data[] = $row;
10093  
10094          /// 5. Select a web service
10095          $row = array();
10096          $url = new moodle_url("/admin/settings.php?section=externalservices");
10097          $row[0] = "5. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10098                          array('href' => $url));
10099          $row[1] = "";
10100          $row[2] = get_string('createservicedescription', 'webservice');
10101          $table->data[] = $row;
10102  
10103          /// 6. Add functions
10104          $row = array();
10105          $url = new moodle_url("/admin/settings.php?section=externalservices");
10106          $row[0] = "6. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10107                          array('href' => $url));
10108          $row[1] = "";
10109          $row[2] = get_string('addfunctionsdescription', 'webservice');
10110          $table->data[] = $row;
10111  
10112          /// 7. Add the specific user
10113          $row = array();
10114          $url = new moodle_url("/admin/settings.php?section=externalservices");
10115          $row[0] = "7. " . html_writer::tag('a', get_string('selectspecificuser', 'webservice'),
10116                          array('href' => $url));
10117          $row[1] = "";
10118          $row[2] = get_string('selectspecificuserdescription', 'webservice');
10119          $table->data[] = $row;
10120  
10121          /// 8. Create token for the specific user
10122          $row = array();
10123          $url = new moodle_url('/admin/webservice/tokens.php', ['action' => 'create']);
10124          $row[0] = "8. " . html_writer::tag('a', get_string('createtokenforuser', 'webservice'),
10125                          array('href' => $url));
10126          $row[1] = "";
10127          $row[2] = get_string('createtokenforuserdescription', 'webservice');
10128          $table->data[] = $row;
10129  
10130          /// 9. Enable the documentation
10131          $row = array();
10132          $url = new moodle_url("/admin/search.php?query=enablewsdocumentation");
10133          $row[0] = "9. " . html_writer::tag('a', get_string('enabledocumentation', 'webservice'),
10134                          array('href' => $url));
10135          $status = '<span class="warning">' . get_string('no') . '</span>';
10136          if ($CFG->enablewsdocumentation) {
10137              $status = get_string('yes');
10138          }
10139          $row[1] = $status;
10140          $row[2] = get_string('enabledocumentationdescription', 'webservice');
10141          $table->data[] = $row;
10142  
10143          /// 10. Test the service
10144          $row = array();
10145          $url = new moodle_url("/admin/webservice/testclient.php");
10146          $row[0] = "10. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10147                          array('href' => $url));
10148          $row[1] = "";
10149          $row[2] = get_string('testwithtestclientdescription', 'webservice');
10150          $table->data[] = $row;
10151  
10152          $return .= html_writer::table($table);
10153  
10154          /// Users as clients with token
10155          $return .= $brtag . $brtag . $brtag;
10156          $return .= $OUTPUT->heading(get_string('userasclients', 'webservice'), 3, 'main');
10157          $table = new html_table();
10158          $table->head = array(get_string('step', 'webservice'), get_string('status'),
10159              get_string('description'));
10160          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
10161          $table->id = 'userasclients';
10162          $table->attributes['class'] = 'admintable wsoverview generaltable';
10163          $table->data = array();
10164  
10165          $return .= $brtag . get_string('userasclientsdescription', 'webservice') .
10166                  $brtag . $brtag;
10167  
10168          /// 1. Enable Web Services
10169          $row = array();
10170          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10171          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10172                          array('href' => $url));
10173          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10174          if ($CFG->enablewebservices) {
10175              $status = get_string('yes');
10176          }
10177          $row[1] = $status;
10178          $row[2] = get_string('enablewsdescription', 'webservice');
10179          $table->data[] = $row;
10180  
10181          /// 2. Enable protocols
10182          $row = array();
10183          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10184          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10185                          array('href' => $url));
10186          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10187          //retrieve activated protocol
10188          $active_protocols = empty($CFG->webserviceprotocols) ?
10189                  array() : explode(',', $CFG->webserviceprotocols);
10190          if (!empty($active_protocols)) {
10191              $status = "";
10192              foreach ($active_protocols as $protocol) {
10193                  $status .= $protocol . $brtag;
10194              }
10195          }
10196          $row[1] = $status;
10197          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10198          $table->data[] = $row;
10199  
10200  
10201          /// 3. Select a web service
10202          $row = array();
10203          $url = new moodle_url("/admin/settings.php?section=externalservices");
10204          $row[0] = "3. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10205                          array('href' => $url));
10206          $row[1] = "";
10207          $row[2] = get_string('createserviceforusersdescription', 'webservice');
10208          $table->data[] = $row;
10209  
10210          /// 4. Add functions
10211          $row = array();
10212          $url = new moodle_url("/admin/settings.php?section=externalservices");
10213          $row[0] = "4. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10214                          array('href' => $url));
10215          $row[1] = "";
10216          $row[2] = get_string('addfunctionsdescription', 'webservice');
10217          $table->data[] = $row;
10218  
10219          /// 5. Add capability to users
10220          $row = array();
10221          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10222          $row[0] = "5. " . html_writer::tag('a', get_string('addcapabilitytousers', 'webservice'),
10223                          array('href' => $url));
10224          $row[1] = "";
10225          $row[2] = get_string('addcapabilitytousersdescription', 'webservice');
10226          $table->data[] = $row;
10227  
10228          /// 6. Test the service
10229          $row = array();
10230          $url = new moodle_url("/admin/webservice/testclient.php");
10231          $row[0] = "6. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10232                          array('href' => $url));
10233          $row[1] = "";
10234          $row[2] = get_string('testauserwithtestclientdescription', 'webservice');
10235          $table->data[] = $row;
10236  
10237          $return .= html_writer::table($table);
10238  
10239          return highlight($query, $return);
10240      }
10241  
10242  }
10243  
10244  
10245  /**
10246   * Special class for web service protocol administration.
10247   *
10248   * @author Petr Skoda (skodak)
10249   */
10250  class admin_setting_managewebserviceprotocols extends admin_setting {
10251  
10252      /**
10253       * Calls parent::__construct with specific arguments
10254       */
10255      public function __construct() {
10256          $this->nosave = true;
10257          parent::__construct('webservicesui', get_string('manageprotocols', 'webservice'), '', '');
10258      }
10259  
10260      /**
10261       * Always returns true, does nothing
10262       *
10263       * @return true
10264       */
10265      public function get_setting() {
10266          return true;
10267      }
10268  
10269      /**
10270       * Always returns true, does nothing
10271       *
10272       * @return true
10273       */
10274      public function get_defaultsetting() {
10275          return true;
10276      }
10277  
10278      /**
10279       * Always returns '', does not write anything
10280       *
10281       * @return string Always returns ''
10282       */
10283      public function write_setting($data) {
10284      // do not write any setting
10285          return '';
10286      }
10287  
10288      /**
10289       * Checks if $query is one of the available webservices
10290       *
10291       * @param string $query The string to search for
10292       * @return bool Returns true if found, false if not
10293       */
10294      public function is_related($query) {
10295          if (parent::is_related($query)) {
10296              return true;
10297          }
10298  
10299          $protocols = core_component::get_plugin_list('webservice');
10300          foreach ($protocols as $protocol=>$location) {
10301              if (strpos($protocol, $query) !== false) {
10302                  return true;
10303              }
10304              $protocolstr = get_string('pluginname', 'webservice_'.$protocol);
10305              if (strpos(core_text::strtolower($protocolstr), $query) !== false) {
10306                  return true;
10307              }
10308          }
10309          return false;
10310      }
10311  
10312      /**
10313       * Builds the XHTML to display the control
10314       *
10315       * @param string $data Unused
10316       * @param string $query
10317       * @return string
10318       */
10319      public function output_html($data, $query='') {
10320          global $CFG, $OUTPUT;
10321  
10322          // display strings
10323          $stradministration = get_string('administration');
10324          $strsettings = get_string('settings');
10325          $stredit = get_string('edit');
10326          $strprotocol = get_string('protocol', 'webservice');
10327          $strenable = get_string('enable');
10328          $strdisable = get_string('disable');
10329          $strversion = get_string('version');
10330  
10331          $protocols_available = core_component::get_plugin_list('webservice');
10332          $active_protocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
10333          ksort($protocols_available);
10334  
10335          foreach ($active_protocols as $key=>$protocol) {
10336              if (empty($protocols_available[$protocol])) {
10337                  unset($active_protocols[$key]);
10338              }
10339          }
10340  
10341          $return = $OUTPUT->heading(get_string('actwebserviceshhdr', 'webservice'), 3, 'main');
10342          $return .= $OUTPUT->box_start('generalbox webservicesui');
10343  
10344          $table = new html_table();
10345          $table->head  = array($strprotocol, $strversion, $strenable, $strsettings);
10346          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
10347          $table->id = 'webserviceprotocols';
10348          $table->attributes['class'] = 'admintable generaltable';
10349          $table->data  = array();
10350  
10351          // iterate through auth plugins and add to the display table
10352          $url = "$CFG->wwwroot/$CFG->admin/webservice/protocols.php?sesskey=" . sesskey();
10353          foreach ($protocols_available as $protocol => $location) {
10354              $name = get_string('pluginname', 'webservice_'.$protocol);
10355  
10356              $plugin = new stdClass();
10357              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/version.php')) {
10358                  include($CFG->dirroot.'/webservice/'.$protocol.'/version.php');
10359              }
10360              $version = isset($plugin->version) ? $plugin->version : '';
10361  
10362              // hide/show link
10363              if (in_array($protocol, $active_protocols)) {
10364                  $hideshow = "<a href=\"$url&amp;action=disable&amp;webservice=$protocol\">";
10365                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
10366                  $displayname = "<span>$name</span>";
10367              } else {
10368                  $hideshow = "<a href=\"$url&amp;action=enable&amp;webservice=$protocol\">";
10369                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
10370                  $displayname = "<span class=\"dimmed_text\">$name</span>";
10371              }
10372  
10373              // settings link
10374              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/settings.php')) {
10375                  $settings = "<a href=\"settings.php?section=webservicesetting$protocol\">$strsettings</a>";
10376              } else {
10377                  $settings = '';
10378              }
10379  
10380              // add a row to the table
10381              $table->data[] = array($displayname, $version, $hideshow, $settings);
10382          }
10383          $return .= html_writer::table($table);
10384          $return .= get_string('configwebserviceplugins', 'webservice');
10385          $return .= $OUTPUT->box_end();
10386  
10387          return highlight($query, $return);
10388      }
10389  }
10390  
10391  /**
10392   * Colour picker
10393   *
10394   * @copyright 2010 Sam Hemelryk
10395   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10396   */
10397  class admin_setting_configcolourpicker extends admin_setting {
10398  
10399      /**
10400       * Information for previewing the colour
10401       *
10402       * @var array|null
10403       */
10404      protected $previewconfig = null;
10405  
10406      /**
10407       * Use default when empty.
10408       */
10409      protected $usedefaultwhenempty = true;
10410  
10411      /**
10412       *
10413       * @param string $name
10414       * @param string $visiblename
10415       * @param string $description
10416       * @param string $defaultsetting
10417       * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor');
10418       */
10419      public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig = null,
10420              $usedefaultwhenempty = true) {
10421          $this->previewconfig = $previewconfig;
10422          $this->usedefaultwhenempty = $usedefaultwhenempty;
10423          parent::__construct($name, $visiblename, $description, $defaultsetting);
10424          $this->set_force_ltr(true);
10425      }
10426  
10427      /**
10428       * Return the setting
10429       *
10430       * @return mixed returns config if successful else null
10431       */
10432      public function get_setting() {
10433          return $this->config_read($this->name);
10434      }
10435  
10436      /**
10437       * Saves the setting
10438       *
10439       * @param string $data
10440       * @return bool
10441       */
10442      public function write_setting($data) {
10443          $data = $this->validate($data);
10444          if ($data === false) {
10445              return  get_string('validateerror', 'admin');
10446          }
10447          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
10448      }
10449  
10450      /**
10451       * Validates the colour that was entered by the user
10452       *
10453       * @param string $data
10454       * @return string|false
10455       */
10456      protected function validate($data) {
10457          /**
10458           * List of valid HTML colour names
10459           *
10460           * @var array
10461           */
10462           $colornames = array(
10463              'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure',
10464              'beige', 'bisque', 'black', 'blanchedalmond', 'blue',
10465              'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse',
10466              'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson',
10467              'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray',
10468              'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta',
10469              'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
10470              'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray',
10471              'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
10472              'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick',
10473              'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro',
10474              'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green',
10475              'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo',
10476              'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen',
10477              'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',
10478              'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen',
10479              'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue',
10480              'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow',
10481              'lime', 'limegreen', 'linen', 'magenta', 'maroon',
10482              'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',
10483              'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
10484              'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',
10485              'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',
10486              'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',
10487              'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',
10488              'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',
10489              'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown',
10490              'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',
10491              'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan',
10492              'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white',
10493              'whitesmoke', 'yellow', 'yellowgreen'
10494          );
10495  
10496          if (preg_match('/^#?([[:xdigit:]]{3}){1,2}$/', $data)) {
10497              if (strpos($data, '#')!==0) {
10498                  $data = '#'.$data;
10499              }
10500              return $data;
10501          } else if (in_array(strtolower($data), $colornames)) {
10502              return $data;
10503          } else if (preg_match('/rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)/i', $data)) {
10504              return $data;
10505          } else if (preg_match('/rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)/i', $data)) {
10506              return $data;
10507          } else if (preg_match('/hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)/i', $data)) {
10508              return $data;
10509          } else if (preg_match('/hsla\(\d{0,3}\, ?\d{0,3}%,\d{0,3}%\, ?\d(\.\d)?\)/i', $data)) {
10510              return $data;
10511          } else if (($data == 'transparent') || ($data == 'currentColor') || ($data == 'inherit')) {
10512              return $data;
10513          } else if (empty($data)) {
10514              if ($this->usedefaultwhenempty){
10515                  return $this->defaultsetting;
10516              } else {
10517                  return '';
10518              }
10519          } else {
10520              return false;
10521          }
10522      }
10523  
10524      /**
10525       * Generates the HTML for the setting
10526       *
10527       * @global moodle_page $PAGE
10528       * @global core_renderer $OUTPUT
10529       * @param string $data
10530       * @param string $query
10531       */
10532      public function output_html($data, $query = '') {
10533          global $PAGE, $OUTPUT;
10534  
10535          $icon = new pix_icon('i/loading', get_string('loading', 'admin'), 'moodle', ['class' => 'loadingicon']);
10536          $context = (object) [
10537              'id' => $this->get_id(),
10538              'name' => $this->get_full_name(),
10539              'value' => $data,
10540              'icon' => $icon->export_for_template($OUTPUT),
10541              'haspreviewconfig' => !empty($this->previewconfig),
10542              'forceltr' => $this->get_force_ltr(),
10543              'readonly' => $this->is_readonly(),
10544          ];
10545  
10546          $element = $OUTPUT->render_from_template('core_admin/setting_configcolourpicker', $context);
10547          $PAGE->requires->js_init_call('M.util.init_colour_picker', array($this->get_id(), $this->previewconfig));
10548  
10549          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '',
10550              $this->get_defaultsetting(), $query);
10551      }
10552  
10553  }
10554  
10555  
10556  /**
10557   * Class used for uploading of one file into file storage,
10558   * the file name is stored in config table.
10559   *
10560   * Please note you need to implement your own '_pluginfile' callback function,
10561   * this setting only stores the file, it does not deal with file serving.
10562   *
10563   * @copyright 2013 Petr Skoda {@link http://skodak.org}
10564   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10565   */
10566  class admin_setting_configstoredfile extends admin_setting {
10567      /** @var array file area options - should be one file only */
10568      protected $options;
10569      /** @var string name of the file area */
10570      protected $filearea;
10571      /** @var int intemid */
10572      protected $itemid;
10573      /** @var string used for detection of changes */
10574      protected $oldhashes;
10575  
10576      /**
10577       * Create new stored file setting.
10578       *
10579       * @param string $name low level setting name
10580       * @param string $visiblename human readable setting name
10581       * @param string $description description of setting
10582       * @param mixed $filearea file area for file storage
10583       * @param int $itemid itemid for file storage
10584       * @param array $options file area options
10585       */
10586      public function __construct($name, $visiblename, $description, $filearea, $itemid = 0, array $options = null) {
10587          parent::__construct($name, $visiblename, $description, '');
10588          $this->filearea = $filearea;
10589          $this->itemid   = $itemid;
10590          $this->options  = (array)$options;
10591          $this->customcontrol = true;
10592      }
10593  
10594      /**
10595       * Applies defaults and returns all options.
10596       * @return array
10597       */
10598      protected function get_options() {
10599          global $CFG;
10600  
10601          require_once("$CFG->libdir/filelib.php");
10602          require_once("$CFG->dirroot/repository/lib.php");
10603          $defaults = array(
10604              'mainfile' => '', 'subdirs' => 0, 'maxbytes' => -1, 'maxfiles' => 1,
10605              'accepted_types' => '*', 'return_types' => FILE_INTERNAL, 'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED,
10606              'context' => context_system::instance());
10607          foreach($this->options as $k => $v) {
10608              $defaults[$k] = $v;
10609          }
10610  
10611          return $defaults;
10612      }
10613  
10614      public function get_setting() {
10615          return $this->config_read($this->name);
10616      }
10617  
10618      public function write_setting($data) {
10619          global $USER;
10620  
10621          // Let's not deal with validation here, this is for admins only.
10622          $current = $this->get_setting();
10623          if (empty($data) && $current === null) {
10624              // This will be the case when applying default settings (installation).
10625              return ($this->config_write($this->name, '') ? '' : get_string('errorsetting', 'admin'));
10626          } else if (!is_number($data)) {
10627              // Draft item id is expected here!
10628              return get_string('errorsetting', 'admin');
10629          }
10630  
10631          $options = $this->get_options();
10632          $fs = get_file_storage();
10633          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10634  
10635          $this->oldhashes = null;
10636          if ($current) {
10637              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10638              if ($file = $fs->get_file_by_hash($hash)) {
10639                  $this->oldhashes = $file->get_contenthash().$file->get_pathnamehash();
10640              }
10641              unset($file);
10642          }
10643  
10644          if ($fs->file_exists($options['context']->id, $component, $this->filearea, $this->itemid, '/', '.')) {
10645              // Make sure the settings form was not open for more than 4 days and draft areas deleted in the meantime.
10646              // But we can safely ignore that if the destination area is empty, so that the user is not prompt
10647              // with an error because the draft area does not exist, as he did not use it.
10648              $usercontext = context_user::instance($USER->id);
10649              if (!$fs->file_exists($usercontext->id, 'user', 'draft', $data, '/', '.') && $current !== '') {
10650                  return get_string('errorsetting', 'admin');
10651              }
10652          }
10653  
10654          file_save_draft_area_files($data, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10655          $files = $fs->get_area_files($options['context']->id, $component, $this->filearea, $this->itemid, 'sortorder,filepath,filename', false);
10656  
10657          $filepath = '';
10658          if ($files) {
10659              /** @var stored_file $file */
10660              $file = reset($files);
10661              $filepath = $file->get_filepath().$file->get_filename();
10662          }
10663  
10664          return ($this->config_write($this->name, $filepath) ? '' : get_string('errorsetting', 'admin'));
10665      }
10666  
10667      public function post_write_settings($original) {
10668          $options = $this->get_options();
10669          $fs = get_file_storage();
10670          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10671  
10672          $current = $this->get_setting();
10673          $newhashes = null;
10674          if ($current) {
10675              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10676              if ($file = $fs->get_file_by_hash($hash)) {
10677                  $newhashes = $file->get_contenthash().$file->get_pathnamehash();
10678              }
10679              unset($file);
10680          }
10681  
10682          if ($this->oldhashes === $newhashes) {
10683              $this->oldhashes = null;
10684              return false;
10685          }
10686          $this->oldhashes = null;
10687  
10688          $callbackfunction = $this->updatedcallback;
10689          if (!empty($callbackfunction) and function_exists($callbackfunction)) {
10690              $callbackfunction($this->get_full_name());
10691          }
10692          return true;
10693      }
10694  
10695      public function output_html($data, $query = '') {
10696          global $PAGE, $CFG;
10697  
10698          $options = $this->get_options();
10699          $id = $this->get_id();
10700          $elname = $this->get_full_name();
10701          $draftitemid = file_get_submitted_draft_itemid($elname);
10702          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10703          file_prepare_draft_area($draftitemid, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10704  
10705          // Filemanager form element implementation is far from optimal, we need to rework this if we ever fix it...
10706          require_once("$CFG->dirroot/lib/form/filemanager.php");
10707  
10708          $fmoptions = new stdClass();
10709          $fmoptions->mainfile       = $options['mainfile'];
10710          $fmoptions->maxbytes       = $options['maxbytes'];
10711          $fmoptions->maxfiles       = $options['maxfiles'];
10712          $fmoptions->client_id      = uniqid();
10713          $fmoptions->itemid         = $draftitemid;
10714          $fmoptions->subdirs        = $options['subdirs'];
10715          $fmoptions->target         = $id;
10716          $fmoptions->accepted_types = $options['accepted_types'];
10717          $fmoptions->return_types   = $options['return_types'];
10718          $fmoptions->context        = $options['context'];
10719          $fmoptions->areamaxbytes   = $options['areamaxbytes'];
10720  
10721          $fm = new form_filemanager($fmoptions);
10722          $output = $PAGE->get_renderer('core', 'files');
10723          $html = $output->render($fm);
10724  
10725          $html .= '<input value="'.$draftitemid.'" name="'.$elname.'" type="hidden" />';
10726          $html .= '<input value="" id="'.$id.'" type="hidden" />';
10727  
10728          return format_admin_setting($this, $this->visiblename,
10729              '<div class="form-filemanager" data-fieldtype="filemanager">'.$html.'</div>',
10730              $this->description, true, '', '', $query);
10731      }
10732  }
10733  
10734  
10735  /**
10736   * Administration interface for user specified regular expressions for device detection.
10737   *
10738   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10739   */
10740  class admin_setting_devicedetectregex extends admin_setting {
10741  
10742      /**
10743       * Calls parent::__construct with specific args
10744       *
10745       * @param string $name
10746       * @param string $visiblename
10747       * @param string $description
10748       * @param mixed $defaultsetting
10749       */
10750      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
10751          global $CFG;
10752          parent::__construct($name, $visiblename, $description, $defaultsetting);
10753      }
10754  
10755      /**
10756       * Return the current setting(s)
10757       *
10758       * @return array Current settings array
10759       */
10760      public function get_setting() {
10761          global $CFG;
10762  
10763          $config = $this->config_read($this->name);
10764          if (is_null($config)) {
10765              return null;
10766          }
10767  
10768          return $this->prepare_form_data($config);
10769      }
10770  
10771      /**
10772       * Save selected settings
10773       *
10774       * @param array $data Array of settings to save
10775       * @return bool
10776       */
10777      public function write_setting($data) {
10778          if (empty($data)) {
10779              $data = array();
10780          }
10781  
10782          if ($this->config_write($this->name, $this->process_form_data($data))) {
10783              return ''; // success
10784          } else {
10785              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
10786          }
10787      }
10788  
10789      /**
10790       * Return XHTML field(s) for regexes
10791       *
10792       * @param array $data Array of options to set in HTML
10793       * @return string XHTML string for the fields and wrapping div(s)
10794       */
10795      public function output_html($data, $query='') {
10796          global $OUTPUT;
10797  
10798          $context = (object) [
10799              'expressions' => [],
10800              'name' => $this->get_full_name()
10801          ];
10802  
10803          if (empty($data)) {
10804              $looplimit = 1;
10805          } else {
10806              $looplimit = (count($data)/2)+1;
10807          }
10808  
10809          for ($i=0; $i<$looplimit; $i++) {
10810  
10811              $expressionname = 'expression'.$i;
10812  
10813              if (!empty($data[$expressionname])){
10814                  $expression = $data[$expressionname];
10815              } else {
10816                  $expression = '';
10817              }
10818  
10819              $valuename = 'value'.$i;
10820  
10821              if (!empty($data[$valuename])){
10822                  $value = $data[$valuename];
10823              } else {
10824                  $value= '';
10825              }
10826  
10827              $context->expressions[] = [
10828                  'index' => $i,
10829                  'expression' => $expression,
10830                  'value' => $value
10831              ];
10832          }
10833  
10834          $element = $OUTPUT->render_from_template('core_admin/setting_devicedetectregex', $context);
10835  
10836          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
10837      }
10838  
10839      /**
10840       * Converts the string of regexes
10841       *
10842       * @see self::process_form_data()
10843       * @param $regexes string of regexes
10844       * @return array of form fields and their values
10845       */
10846      protected function prepare_form_data($regexes) {
10847  
10848          $regexes = json_decode($regexes);
10849  
10850          $form = array();
10851  
10852          $i = 0;
10853  
10854          foreach ($regexes as $value => $regex) {
10855              $expressionname  = 'expression'.$i;
10856              $valuename = 'value'.$i;
10857  
10858              $form[$expressionname] = $regex;
10859              $form[$valuename] = $value;
10860              $i++;
10861          }
10862  
10863          return $form;
10864      }
10865  
10866      /**
10867       * Converts the data from admin settings form into a string of regexes
10868       *
10869       * @see self::prepare_form_data()
10870       * @param array $data array of admin form fields and values
10871       * @return false|string of regexes
10872       */
10873      protected function process_form_data(array $form) {
10874  
10875          $count = count($form); // number of form field values
10876  
10877          if ($count % 2) {
10878              // we must get five fields per expression
10879              return false;
10880          }
10881  
10882          $regexes = array();
10883          for ($i = 0; $i < $count / 2; $i++) {
10884              $expressionname  = "expression".$i;
10885              $valuename       = "value".$i;
10886  
10887              $expression = trim($form['expression'.$i]);
10888              $value      = trim($form['value'.$i]);
10889  
10890              if (empty($expression)){
10891                  continue;
10892              }
10893  
10894              $regexes[$value] = $expression;
10895          }
10896  
10897          $regexes = json_encode($regexes);
10898  
10899          return $regexes;
10900      }
10901  
10902  }
10903  
10904  /**
10905   * Multiselect for current modules
10906   *
10907   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10908   */
10909  class admin_setting_configmultiselect_modules extends admin_setting_configmultiselect {
10910      private $excludesystem;
10911  
10912      /**
10913       * Calls parent::__construct - note array $choices is not required
10914       *
10915       * @param string $name setting name
10916       * @param string $visiblename localised setting name
10917       * @param string $description setting description
10918       * @param array $defaultsetting a plain array of default module ids
10919       * @param bool $excludesystem If true, excludes modules with 'system' archetype
10920       */
10921      public function __construct($name, $visiblename, $description, $defaultsetting = array(),
10922              $excludesystem = true) {
10923          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
10924          $this->excludesystem = $excludesystem;
10925      }
10926  
10927      /**
10928       * Loads an array of current module choices
10929       *
10930       * @return bool always return true
10931       */
10932      public function load_choices() {
10933          if (is_array($this->choices)) {
10934              return true;
10935          }
10936          $this->choices = array();
10937  
10938          global $CFG, $DB;
10939          $records = $DB->get_records('modules', array('visible'=>1), 'name');
10940          foreach ($records as $record) {
10941              // Exclude modules if the code doesn't exist
10942              if (file_exists("$CFG->dirroot/mod/$record->name/lib.php")) {
10943                  // Also exclude system modules (if specified)
10944                  if (!($this->excludesystem &&
10945                          plugin_supports('mod', $record->name, FEATURE_MOD_ARCHETYPE) ===
10946                          MOD_ARCHETYPE_SYSTEM)) {
10947                      $this->choices[$record->id] = $record->name;
10948                  }
10949              }
10950          }
10951          return true;
10952      }
10953  }
10954  
10955  /**
10956   * Admin setting to show if a php extension is enabled or not.
10957   *
10958   * @copyright 2013 Damyon Wiese
10959   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10960   */
10961  class admin_setting_php_extension_enabled extends admin_setting {
10962  
10963      /** @var string The name of the extension to check for */
10964      private $extension;
10965  
10966      /**
10967       * Calls parent::__construct with specific arguments
10968       */
10969      public function __construct($name, $visiblename, $description, $extension) {
10970          $this->extension = $extension;
10971          $this->nosave = true;
10972          parent::__construct($name, $visiblename, $description, '');
10973      }
10974  
10975      /**
10976       * Always returns true, does nothing
10977       *
10978       * @return true
10979       */
10980      public function get_setting() {
10981          return true;
10982      }
10983  
10984      /**
10985       * Always returns true, does nothing
10986       *
10987       * @return true
10988       */
10989      public function get_defaultsetting() {
10990          return true;
10991      }
10992  
10993      /**
10994       * Always returns '', does not write anything
10995       *
10996       * @return string Always returns ''
10997       */
10998      public function write_setting($data) {
10999          // Do not write any setting.
11000          return '';
11001      }
11002  
11003      /**
11004       * Outputs the html for this setting.
11005       * @return string Returns an XHTML string
11006       */
11007      public function output_html($data, $query='') {
11008          global $OUTPUT;
11009  
11010          $o = '';
11011          if (!extension_loaded($this->extension)) {
11012              $warning = $OUTPUT->pix_icon('i/warning', '', '', array('role' => 'presentation')) . ' ' . $this->description;
11013  
11014              $o .= format_admin_setting($this, $this->visiblename, $warning);
11015          }
11016          return $o;
11017      }
11018  }
11019  
11020  /**
11021   * Server timezone setting.
11022   *
11023   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11024   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11025   * @author    Petr Skoda <petr.skoda@totaralms.com>
11026   */
11027  class admin_setting_servertimezone extends admin_setting_configselect {
11028      /**
11029       * Constructor.
11030       */
11031      public function __construct() {
11032          $default = core_date::get_default_php_timezone();
11033          if ($default === 'UTC') {
11034              // Nobody really wants UTC, so instead default selection to the country that is confused by the UTC the most.
11035              $default = 'Europe/London';
11036          }
11037  
11038          parent::__construct('timezone',
11039              new lang_string('timezone', 'core_admin'),
11040              new lang_string('configtimezone', 'core_admin'), $default, null);
11041      }
11042  
11043      /**
11044       * Lazy load timezone options.
11045       * @return bool true if loaded, false if error
11046       */
11047      public function load_choices() {
11048          global $CFG;
11049          if (is_array($this->choices)) {
11050              return true;
11051          }
11052  
11053          $current = isset($CFG->timezone) ? $CFG->timezone : null;
11054          $this->choices = core_date::get_list_of_timezones($current, false);
11055          if ($current == 99) {
11056              // Do not show 99 unless it is current value, we want to get rid of it over time.
11057              $this->choices['99'] = new lang_string('timezonephpdefault', 'core_admin',
11058                  core_date::get_default_php_timezone());
11059          }
11060  
11061          return true;
11062      }
11063  }
11064  
11065  /**
11066   * Forced user timezone setting.
11067   *
11068   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11069   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11070   * @author    Petr Skoda <petr.skoda@totaralms.com>
11071   */
11072  class admin_setting_forcetimezone extends admin_setting_configselect {
11073      /**
11074       * Constructor.
11075       */
11076      public function __construct() {
11077          parent::__construct('forcetimezone',
11078              new lang_string('forcetimezone', 'core_admin'),
11079              new lang_string('helpforcetimezone', 'core_admin'), '99', null);
11080      }
11081  
11082      /**
11083       * Lazy load timezone options.
11084       * @return bool true if loaded, false if error
11085       */
11086      public function load_choices() {
11087          global $CFG;
11088          if (is_array($this->choices)) {
11089              return true;
11090          }
11091  
11092          $current = isset($CFG->forcetimezone) ? $CFG->forcetimezone : null;
11093          $this->choices = core_date::get_list_of_timezones($current, true);
11094          $this->choices['99'] = new lang_string('timezonenotforced', 'core_admin');
11095  
11096          return true;
11097      }
11098  }
11099  
11100  
11101  /**
11102   * Search setup steps info.
11103   *
11104   * @package core
11105   * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
11106   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11107   */
11108  class admin_setting_searchsetupinfo extends admin_setting {
11109  
11110      /**
11111       * Calls parent::__construct with specific arguments
11112       */
11113      public function __construct() {
11114          $this->nosave = true;
11115          parent::__construct('searchsetupinfo', '', '', '');
11116      }
11117  
11118      /**
11119       * Always returns true, does nothing
11120       *
11121       * @return true
11122       */
11123      public function get_setting() {
11124          return true;
11125      }
11126  
11127      /**
11128       * Always returns true, does nothing
11129       *
11130       * @return true
11131       */
11132      public function get_defaultsetting() {
11133          return true;
11134      }
11135  
11136      /**
11137       * Always returns '', does not write anything
11138       *
11139       * @param array $data
11140       * @return string Always returns ''
11141       */
11142      public function write_setting($data) {
11143          // Do not write any setting.
11144          return '';
11145      }
11146  
11147      /**
11148       * Builds the HTML to display the control
11149       *
11150       * @param string $data Unused
11151       * @param string $query
11152       * @return string
11153       */
11154      public function output_html($data, $query='') {
11155          global $CFG, $OUTPUT, $ADMIN;
11156  
11157          $return = '';
11158          $brtag = html_writer::empty_tag('br');
11159  
11160          $searchareas = \core_search\manager::get_search_areas_list();
11161          $anyenabled = !empty(\core_search\manager::get_search_areas_list(true));
11162          $anyindexed = false;
11163          foreach ($searchareas as $areaid => $searcharea) {
11164              list($componentname, $varname) = $searcharea->get_config_var_name();
11165              if (get_config($componentname, $varname . '_indexingstart')) {
11166                  $anyindexed = true;
11167                  break;
11168              }
11169          }
11170  
11171          $return .= $OUTPUT->heading(get_string('searchsetupinfo', 'admin'), 3, 'main');
11172  
11173          $table = new html_table();
11174          $table->head = array(get_string('step', 'search'), get_string('status'));
11175          $table->colclasses = array('leftalign step', 'leftalign status');
11176          $table->id = 'searchsetup';
11177          $table->attributes['class'] = 'admintable generaltable';
11178          $table->data = array();
11179  
11180          $return .= $brtag . get_string('searchsetupdescription', 'search') . $brtag . $brtag;
11181  
11182          // Select a search engine.
11183          $row = array();
11184          $url = new moodle_url('/admin/settings.php?section=manageglobalsearch#admin-searchengine');
11185          $row[0] = '1. ' . html_writer::tag('a', get_string('selectsearchengine', 'admin'),
11186                          array('href' => $url));
11187  
11188          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11189          if (!empty($CFG->searchengine)) {
11190              $status = html_writer::tag('span', get_string('pluginname', 'search_' . $CFG->searchengine),
11191                  array('class' => 'badge badge-success'));
11192  
11193          }
11194          $row[1] = $status;
11195          $table->data[] = $row;
11196  
11197          // Available areas.
11198          $row = array();
11199          $url = new moodle_url('/admin/searchareas.php');
11200          $row[0] = '2. ' . html_writer::tag('a', get_string('enablesearchareas', 'admin'),
11201                          array('href' => $url));
11202  
11203          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11204          if ($anyenabled) {
11205              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11206  
11207          }
11208          $row[1] = $status;
11209          $table->data[] = $row;
11210  
11211          // Setup search engine.
11212          $row = array();
11213          if (empty($CFG->searchengine)) {
11214              $row[0] = '3. ' . get_string('setupsearchengine', 'admin');
11215              $row[1] = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11216          } else {
11217              if ($ADMIN->locate('search' . $CFG->searchengine)) {
11218                  $url = new moodle_url('/admin/settings.php?section=search' . $CFG->searchengine);
11219                  $row[0] = '3. ' . html_writer::link($url, get_string('setupsearchengine', 'core_admin'));
11220              } else {
11221                  $row[0] = '3. ' . get_string('setupsearchengine', 'core_admin');
11222              }
11223  
11224              // Check the engine status.
11225              $searchengine = \core_search\manager::search_engine_instance();
11226              try {
11227                  $serverstatus = $searchengine->is_server_ready();
11228              } catch (\moodle_exception $e) {
11229                  $serverstatus = $e->getMessage();
11230              }
11231              if ($serverstatus === true) {
11232                  $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11233              } else {
11234                  $status = html_writer::tag('span', $serverstatus, array('class' => 'badge badge-danger'));
11235              }
11236              $row[1] = $status;
11237          }
11238          $table->data[] = $row;
11239  
11240          // Indexed data.
11241          $row = array();
11242          $url = new moodle_url('/admin/searchareas.php');
11243          $row[0] = '4. ' . html_writer::tag('a', get_string('indexdata', 'admin'), array('href' => $url));
11244          if ($anyindexed) {
11245              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11246          } else {
11247              $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11248          }
11249          $row[1] = $status;
11250          $table->data[] = $row;
11251  
11252          // Enable global search.
11253          $row = array();
11254          $url = new moodle_url("/admin/search.php?query=enableglobalsearch");
11255          $row[0] = '5. ' . html_writer::tag('a', get_string('enableglobalsearch', 'admin'),
11256                          array('href' => $url));
11257          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11258          if (\core_search\manager::is_global_search_enabled()) {
11259              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11260          }
11261          $row[1] = $status;
11262          $table->data[] = $row;
11263  
11264          $return .= html_writer::table($table);
11265  
11266          return highlight($query, $return);
11267      }
11268  
11269  }
11270  
11271  /**
11272   * Used to validate the contents of SCSS code and ensuring they are parsable.
11273   *
11274   * It does not attempt to detect undefined SCSS variables because it is designed
11275   * to be used without knowledge of other config/scss included.
11276   *
11277   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11278   * @copyright 2016 Dan Poltawski <dan@moodle.com>
11279   */
11280  class admin_setting_scsscode extends admin_setting_configtextarea {
11281  
11282      /**
11283       * Validate the contents of the SCSS to ensure its parsable. Does not
11284       * attempt to detect undefined scss variables.
11285       *
11286       * @param string $data The scss code from text field.
11287       * @return mixed bool true for success or string:error on failure.
11288       */
11289      public function validate($data) {
11290          if (empty($data)) {
11291              return true;
11292          }
11293  
11294          $scss = new core_scss();
11295          try {
11296              $scss->compile($data);
11297          } catch (ScssPhp\ScssPhp\Exception\ParserException $e) {
11298              return get_string('scssinvalid', 'admin', $e->getMessage());
11299          } catch (ScssPhp\ScssPhp\Exception\CompilerException $e) {
11300              // Silently ignore this - it could be a scss variable defined from somewhere
11301              // else which we are not examining here.
11302              return true;
11303          }
11304  
11305          return true;
11306      }
11307  }
11308  
11309  
11310  /**
11311   * Administration setting to define a list of file types.
11312   *
11313   * @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
11314   * @copyright 2017 David Mudrák <david@moodle.com>
11315   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11316   */
11317  class admin_setting_filetypes extends admin_setting_configtext {
11318  
11319      /** @var array Allow selection from these file types only. */
11320      protected $onlytypes = [];
11321  
11322      /** @var bool Allow selection of 'All file types' (will be stored as '*'). */
11323      protected $allowall = true;
11324  
11325      /** @var core_form\filetypes_util instance to use as a helper. */
11326      protected $util = null;
11327  
11328      /**
11329       * Constructor.
11330       *
11331       * @param string $name Unique ascii name like 'mycoresetting' or 'myplugin/mysetting'
11332       * @param string $visiblename Localised label of the setting
11333       * @param string $description Localised description of the setting
11334       * @param string $defaultsetting Default setting value.
11335       * @param array $options Setting widget options, an array with optional keys:
11336       *   'onlytypes' => array Allow selection from these file types only; for example ['onlytypes' => ['web_image']].
11337       *   'allowall' => bool Allow to select 'All file types', defaults to true. Does not apply if onlytypes are set.
11338       */
11339      public function __construct($name, $visiblename, $description, $defaultsetting = '', array $options = []) {
11340  
11341          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW);
11342  
11343          if (array_key_exists('onlytypes', $options) && is_array($options['onlytypes'])) {
11344              $this->onlytypes = $options['onlytypes'];
11345          }
11346  
11347          if (!$this->onlytypes && array_key_exists('allowall', $options)) {
11348              $this->allowall = (bool)$options['allowall'];
11349          }
11350  
11351          $this->util = new \core_form\filetypes_util();
11352      }
11353  
11354      /**
11355       * Normalize the user's input and write it to the database as comma separated list.
11356       *
11357       * Comma separated list as a text representation of the array was chosen to
11358       * make this compatible with how the $CFG->courseoverviewfilesext values are stored.
11359       *
11360       * @param string $data Value submitted by the admin.
11361       * @return string Epty string if all good, error message otherwise.
11362       */
11363      public function write_setting($data) {
11364          return parent::write_setting(implode(',', $this->util->normalize_file_types($data)));
11365      }
11366  
11367      /**
11368       * Validate data before storage
11369       *
11370       * @param string $data The setting values provided by the admin
11371       * @return bool|string True if ok, the string if error found
11372       */
11373      public function validate($data) {
11374  
11375          // No need to call parent's validation here as we are PARAM_RAW.
11376  
11377          if ($this->util->is_listed($data, $this->onlytypes)) {
11378              return true;
11379  
11380          } else {
11381              $troublemakers = $this->util->get_not_listed($data, $this->onlytypes);
11382              return get_string('filetypesnotallowed', 'core_form', implode(' ', $troublemakers));
11383          }
11384      }
11385  
11386      /**
11387       * Return an HTML string for the setting element.
11388       *
11389       * @param string $data The current setting value
11390       * @param string $query Admin search query to be highlighted
11391       * @return string HTML to be displayed
11392       */
11393      public function output_html($data, $query='') {
11394          global $OUTPUT, $PAGE;
11395  
11396          $default = $this->get_defaultsetting();
11397          $context = (object) [
11398              'id' => $this->get_id(),
11399              'name' => $this->get_full_name(),
11400              'value' => $data,
11401              'descriptions' => $this->util->describe_file_types($data),
11402          ];
11403          $element = $OUTPUT->render_from_template('core_admin/setting_filetypes', $context);
11404  
11405          $PAGE->requires->js_call_amd('core_form/filetypes', 'init', [
11406              $this->get_id(),
11407              $this->visiblename->out(),
11408              $this->onlytypes,
11409              $this->allowall,
11410          ]);
11411  
11412          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
11413      }
11414  
11415      /**
11416       * Should the values be always displayed in LTR mode?
11417       *
11418       * We always return true here because these values are not RTL compatible.
11419       *
11420       * @return bool True because these values are not RTL compatible.
11421       */
11422      public function get_force_ltr() {
11423          return true;
11424      }
11425  }
11426  
11427  /**
11428   * Used to validate the content and format of the age of digital consent map and ensuring it is parsable.
11429   *
11430   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11431   * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
11432   */
11433  class admin_setting_agedigitalconsentmap extends admin_setting_configtextarea {
11434  
11435      /**
11436       * Constructor.
11437       *
11438       * @param string $name
11439       * @param string $visiblename
11440       * @param string $description
11441       * @param mixed $defaultsetting string or array
11442       * @param mixed $paramtype
11443       * @param string $cols
11444       * @param string $rows
11445       */
11446      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype = PARAM_RAW,
11447                                  $cols = '60', $rows = '8') {
11448          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
11449          // Pre-set force LTR to false.
11450          $this->set_force_ltr(false);
11451      }
11452  
11453      /**
11454       * Validate the content and format of the age of digital consent map to ensure it is parsable.
11455       *
11456       * @param string $data The age of digital consent map from text field.
11457       * @return mixed bool true for success or string:error on failure.
11458       */
11459      public function validate($data) {
11460          if (empty($data)) {
11461              return true;
11462          }
11463  
11464          try {
11465              \core_auth\digital_consent::parse_age_digital_consent_map($data);
11466          } catch (\moodle_exception $e) {
11467              return get_string('invalidagedigitalconsent', 'admin', $e->getMessage());
11468          }
11469  
11470          return true;
11471      }
11472  }
11473  
11474  /**
11475   * Selection of plugins that can work as site policy handlers
11476   *
11477   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11478   * @copyright 2018 Marina Glancy
11479   */
11480  class admin_settings_sitepolicy_handler_select extends admin_setting_configselect {
11481  
11482      /**
11483       * Constructor
11484       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11485       *        for ones in config_plugins.
11486       * @param string $visiblename localised
11487       * @param string $description long localised info
11488       * @param string $defaultsetting
11489       */
11490      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11491          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11492      }
11493  
11494      /**
11495       * Lazy-load the available choices for the select box
11496       */
11497      public function load_choices() {
11498          if (during_initial_install()) {
11499              return false;
11500          }
11501          if (is_array($this->choices)) {
11502              return true;
11503          }
11504  
11505          $this->choices = ['' => new lang_string('sitepolicyhandlercore', 'core_admin')];
11506          $manager = new \core_privacy\local\sitepolicy\manager();
11507          $plugins = $manager->get_all_handlers();
11508          foreach ($plugins as $pname => $unused) {
11509              $this->choices[$pname] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11510                  ['name' => new lang_string('pluginname', $pname), 'component' => $pname]);
11511          }
11512  
11513          return true;
11514      }
11515  }
11516  
11517  /**
11518   * Used to validate theme presets code and ensuring they compile well.
11519   *
11520   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11521   * @copyright 2019 Bas Brands <bas@moodle.com>
11522   */
11523  class admin_setting_configthemepreset extends admin_setting_configselect {
11524  
11525      /** @var string The name of the theme to check for */
11526      private $themename;
11527  
11528      /**
11529       * Constructor
11530       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
11531       * or 'myplugin/mysetting' for ones in config_plugins.
11532       * @param string $visiblename localised
11533       * @param string $description long localised info
11534       * @param string|int $defaultsetting
11535       * @param array $choices array of $value=>$label for each selection
11536       * @param string $themename name of theme to check presets for.
11537       */
11538      public function __construct($name, $visiblename, $description, $defaultsetting, $choices, $themename) {
11539          $this->themename = $themename;
11540          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
11541      }
11542  
11543      /**
11544       * Write settings if validated
11545       *
11546       * @param string $data
11547       * @return string
11548       */
11549      public function write_setting($data) {
11550          $validated = $this->validate($data);
11551          if ($validated !== true) {
11552              return $validated;
11553          }
11554          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
11555      }
11556  
11557      /**
11558       * Validate the preset file to ensure its parsable.
11559       *
11560       * @param string $data The preset file chosen.
11561       * @return mixed bool true for success or string:error on failure.
11562       */
11563      public function validate($data) {
11564  
11565          if (in_array($data, ['default.scss', 'plain.scss'])) {
11566              return true;
11567          }
11568  
11569          $fs = get_file_storage();
11570          $theme = theme_config::load($this->themename);
11571          $context = context_system::instance();
11572  
11573          // If the preset has not changed there is no need to validate it.
11574          if ($theme->settings->preset == $data) {
11575              return true;
11576          }
11577  
11578          if ($presetfile = $fs->get_file($context->id, 'theme_' . $this->themename, 'preset', 0, '/', $data)) {
11579              // This operation uses a lot of resources.
11580              raise_memory_limit(MEMORY_EXTRA);
11581              core_php_time_limit::raise(300);
11582  
11583              // TODO: MDL-62757 When changing anything in this method please do not forget to check
11584              // if the get_css_content_from_scss() method in class theme_config needs updating too.
11585  
11586              $compiler = new core_scss();
11587              $compiler->prepend_raw_scss($theme->get_pre_scss_code());
11588              $compiler->append_raw_scss($presetfile->get_content());
11589              if ($scssproperties = $theme->get_scss_property()) {
11590                  $compiler->setImportPaths($scssproperties[0]);
11591              }
11592              $compiler->append_raw_scss($theme->get_extra_scss_code());
11593  
11594              try {
11595                  $compiler->to_css();
11596              } catch (Exception $e) {
11597                  return get_string('invalidthemepreset', 'admin', $e->getMessage());
11598              }
11599  
11600              // Try to save memory.
11601              $compiler = null;
11602              unset($compiler);
11603          }
11604  
11605          return true;
11606      }
11607  }
11608  
11609  /**
11610   * Selection of plugins that can work as H5P libraries handlers
11611   *
11612   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11613   * @copyright 2020 Sara Arjona <sara@moodle.com>
11614   */
11615  class admin_settings_h5plib_handler_select extends admin_setting_configselect {
11616  
11617      /**
11618       * Constructor
11619       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11620       *        for ones in config_plugins.
11621       * @param string $visiblename localised
11622       * @param string $description long localised info
11623       * @param string $defaultsetting
11624       */
11625      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11626          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11627      }
11628  
11629      /**
11630       * Lazy-load the available choices for the select box
11631       */
11632      public function load_choices() {
11633          if (during_initial_install()) {
11634              return false;
11635          }
11636          if (is_array($this->choices)) {
11637              return true;
11638          }
11639  
11640          $this->choices = \core_h5p\local\library\autoloader::get_all_handlers();
11641          foreach ($this->choices as $name => $class) {
11642              $this->choices[$name] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11643                  ['name' => new lang_string('pluginname', $name), 'component' => $name]);
11644          }
11645  
11646          return true;
11647      }
11648  }