Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
/lib/ -> adminlib.php (source)

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * 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  use core_admin\local\settings\linkable_settings_page;
 106  
 107  defined('MOODLE_INTERNAL') || die();
 108  
 109  /// Add libraries
 110  require_once($CFG->libdir.'/ddllib.php');
 111  require_once($CFG->libdir.'/xmlize.php');
 112  require_once($CFG->libdir.'/messagelib.php');
 113  
 114  // Add classes, traits, and interfaces which should be autoloaded.
 115  // The autoloader is configured late in setup.php, after ABORT_AFTER_CONFIG.
 116  // This is also required where the setup system is not included at all.
 117  require_once($CFG->dirroot.'/'.$CFG->admin.'/classes/local/settings/linkable_settings_page.php');
 118  
 119  define('INSECURE_DATAROOT_WARNING', 1);
 120  define('INSECURE_DATAROOT_ERROR', 2);
 121  
 122  /**
 123   * Automatically clean-up all plugin data and remove the plugin DB tables
 124   *
 125   * NOTE: do not call directly, use new /admin/plugins.php?uninstall=component instead!
 126   *
 127   * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
 128   * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
 129   * @uses global $OUTPUT to produce notices and other messages
 130   * @return void
 131   */
 132  function uninstall_plugin($type, $name) {
 133      global $CFG, $DB, $OUTPUT;
 134  
 135      // This may take a long time.
 136      core_php_time_limit::raise();
 137  
 138      // Recursively uninstall all subplugins first.
 139      $subplugintypes = core_component::get_plugin_types_with_subplugins();
 140      if (isset($subplugintypes[$type])) {
 141          $base = core_component::get_plugin_directory($type, $name);
 142  
 143          $subpluginsfile = "{$base}/db/subplugins.json";
 144          if (file_exists($subpluginsfile)) {
 145              $subplugins = (array) json_decode(file_get_contents($subpluginsfile))->plugintypes;
 146          } else if (file_exists("{$base}/db/subplugins.php")) {
 147              debugging('Use of subplugins.php has been deprecated. ' .
 148                      'Please update your plugin to provide a subplugins.json file instead.',
 149                      DEBUG_DEVELOPER);
 150              $subplugins = [];
 151              include("{$base}/db/subplugins.php");
 152          }
 153  
 154          if (!empty($subplugins)) {
 155              foreach (array_keys($subplugins) as $subplugintype) {
 156                  $instances = core_component::get_plugin_list($subplugintype);
 157                  foreach ($instances as $subpluginname => $notusedpluginpath) {
 158                      uninstall_plugin($subplugintype, $subpluginname);
 159                  }
 160              }
 161          }
 162      }
 163  
 164      $component = $type . '_' . $name;  // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
 165  
 166      if ($type === 'mod') {
 167          $pluginname = $name;  // eg. 'forum'
 168          if (get_string_manager()->string_exists('modulename', $component)) {
 169              $strpluginname = get_string('modulename', $component);
 170          } else {
 171              $strpluginname = $component;
 172          }
 173  
 174      } else {
 175          $pluginname = $component;
 176          if (get_string_manager()->string_exists('pluginname', $component)) {
 177              $strpluginname = get_string('pluginname', $component);
 178          } else {
 179              $strpluginname = $component;
 180          }
 181      }
 182  
 183      echo $OUTPUT->heading($pluginname);
 184  
 185      // Delete all tag areas, collections and instances associated with this plugin.
 186      core_tag_area::uninstall($component);
 187  
 188      // Custom plugin uninstall.
 189      $plugindirectory = core_component::get_plugin_directory($type, $name);
 190      $uninstalllib = $plugindirectory . '/db/uninstall.php';
 191      if (file_exists($uninstalllib)) {
 192          require_once($uninstalllib);
 193          $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall';    // eg. 'xmldb_workshop_uninstall()'
 194          if (function_exists($uninstallfunction)) {
 195              // Do not verify result, let plugin complain if necessary.
 196              $uninstallfunction();
 197          }
 198      }
 199  
 200      // Specific plugin type cleanup.
 201      $plugininfo = core_plugin_manager::instance()->get_plugin_info($component);
 202      if ($plugininfo) {
 203          $plugininfo->uninstall_cleanup();
 204          core_plugin_manager::reset_caches();
 205      }
 206      $plugininfo = null;
 207  
 208      // perform clean-up task common for all the plugin/subplugin types
 209  
 210      //delete the web service functions and pre-built services
 211      require_once($CFG->dirroot.'/lib/externallib.php');
 212      external_delete_descriptions($component);
 213  
 214      // delete calendar events
 215      $DB->delete_records('event', array('modulename' => $pluginname));
 216      $DB->delete_records('event', ['component' => $component]);
 217  
 218      // Delete scheduled tasks.
 219      $DB->delete_records('task_adhoc', ['component' => $component]);
 220      $DB->delete_records('task_scheduled', array('component' => $component));
 221  
 222      // Delete Inbound Message datakeys.
 223      $DB->delete_records_select('messageinbound_datakeys',
 224              'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($component));
 225  
 226      // Delete Inbound Message handlers.
 227      $DB->delete_records('messageinbound_handlers', array('component' => $component));
 228  
 229      // delete all the logs
 230      $DB->delete_records('log', array('module' => $pluginname));
 231  
 232      // delete log_display information
 233      $DB->delete_records('log_display', array('component' => $component));
 234  
 235      // delete the module configuration records
 236      unset_all_config_for_plugin($component);
 237      if ($type === 'mod') {
 238          unset_all_config_for_plugin($pluginname);
 239      }
 240  
 241      // delete message provider
 242      message_provider_uninstall($component);
 243  
 244      // delete the plugin tables
 245      $xmldbfilepath = $plugindirectory . '/db/install.xml';
 246      drop_plugin_tables($component, $xmldbfilepath, false);
 247      if ($type === 'mod' or $type === 'block') {
 248          // non-frankenstyle table prefixes
 249          drop_plugin_tables($name, $xmldbfilepath, false);
 250      }
 251  
 252      // delete the capabilities that were defined by this module
 253      capabilities_cleanup($component);
 254  
 255      // Delete all remaining files in the filepool owned by the component.
 256      $fs = get_file_storage();
 257      $fs->delete_component_files($component);
 258  
 259      // Finally purge all caches.
 260      purge_all_caches();
 261  
 262      // Invalidate the hash used for upgrade detections.
 263      set_config('allversionshash', '');
 264  
 265      echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
 266  }
 267  
 268  /**
 269   * Returns the version of installed component
 270   *
 271   * @param string $component component name
 272   * @param string $source either 'disk' or 'installed' - where to get the version information from
 273   * @return string|bool version number or false if the component is not found
 274   */
 275  function get_component_version($component, $source='installed') {
 276      global $CFG, $DB;
 277  
 278      list($type, $name) = core_component::normalize_component($component);
 279  
 280      // moodle core or a core subsystem
 281      if ($type === 'core') {
 282          if ($source === 'installed') {
 283              if (empty($CFG->version)) {
 284                  return false;
 285              } else {
 286                  return $CFG->version;
 287              }
 288          } else {
 289              if (!is_readable($CFG->dirroot.'/version.php')) {
 290                  return false;
 291              } else {
 292                  $version = null; //initialize variable for IDEs
 293                  include($CFG->dirroot.'/version.php');
 294                  return $version;
 295              }
 296          }
 297      }
 298  
 299      // activity module
 300      if ($type === 'mod') {
 301          if ($source === 'installed') {
 302              if ($CFG->version < 2013092001.02) {
 303                  return $DB->get_field('modules', 'version', array('name'=>$name));
 304              } else {
 305                  return get_config('mod_'.$name, 'version');
 306              }
 307  
 308          } else {
 309              $mods = core_component::get_plugin_list('mod');
 310              if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
 311                  return false;
 312              } else {
 313                  $plugin = new stdClass();
 314                  $plugin->version = null;
 315                  $module = $plugin;
 316                  include($mods[$name].'/version.php');
 317                  return $plugin->version;
 318              }
 319          }
 320      }
 321  
 322      // block
 323      if ($type === 'block') {
 324          if ($source === 'installed') {
 325              if ($CFG->version < 2013092001.02) {
 326                  return $DB->get_field('block', 'version', array('name'=>$name));
 327              } else {
 328                  return get_config('block_'.$name, 'version');
 329              }
 330          } else {
 331              $blocks = core_component::get_plugin_list('block');
 332              if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
 333                  return false;
 334              } else {
 335                  $plugin = new stdclass();
 336                  include($blocks[$name].'/version.php');
 337                  return $plugin->version;
 338              }
 339          }
 340      }
 341  
 342      // all other plugin types
 343      if ($source === 'installed') {
 344          return get_config($type.'_'.$name, 'version');
 345      } else {
 346          $plugins = core_component::get_plugin_list($type);
 347          if (empty($plugins[$name])) {
 348              return false;
 349          } else {
 350              $plugin = new stdclass();
 351              include($plugins[$name].'/version.php');
 352              return $plugin->version;
 353          }
 354      }
 355  }
 356  
 357  /**
 358   * Delete all plugin tables
 359   *
 360   * @param string $name Name of plugin, used as table prefix
 361   * @param string $file Path to install.xml file
 362   * @param bool $feedback defaults to true
 363   * @return bool Always returns true
 364   */
 365  function drop_plugin_tables($name, $file, $feedback=true) {
 366      global $CFG, $DB;
 367  
 368      // first try normal delete
 369      if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
 370          return true;
 371      }
 372  
 373      // then try to find all tables that start with name and are not in any xml file
 374      $used_tables = get_used_table_names();
 375  
 376      $tables = $DB->get_tables();
 377  
 378      /// Iterate over, fixing id fields as necessary
 379      foreach ($tables as $table) {
 380          if (in_array($table, $used_tables)) {
 381              continue;
 382          }
 383  
 384          if (strpos($table, $name) !== 0) {
 385              continue;
 386          }
 387  
 388          // found orphan table --> delete it
 389          if ($DB->get_manager()->table_exists($table)) {
 390              $xmldb_table = new xmldb_table($table);
 391              $DB->get_manager()->drop_table($xmldb_table);
 392          }
 393      }
 394  
 395      return true;
 396  }
 397  
 398  /**
 399   * Returns names of all known tables == tables that moodle knows about.
 400   *
 401   * @return array Array of lowercase table names
 402   */
 403  function get_used_table_names() {
 404      $table_names = array();
 405      $dbdirs = get_db_directories();
 406  
 407      foreach ($dbdirs as $dbdir) {
 408          $file = $dbdir.'/install.xml';
 409  
 410          $xmldb_file = new xmldb_file($file);
 411  
 412          if (!$xmldb_file->fileExists()) {
 413              continue;
 414          }
 415  
 416          $loaded    = $xmldb_file->loadXMLStructure();
 417          $structure = $xmldb_file->getStructure();
 418  
 419          if ($loaded and $tables = $structure->getTables()) {
 420              foreach($tables as $table) {
 421                  $table_names[] = strtolower($table->getName());
 422              }
 423          }
 424      }
 425  
 426      return $table_names;
 427  }
 428  
 429  /**
 430   * Returns list of all directories where we expect install.xml files
 431   * @return array Array of paths
 432   */
 433  function get_db_directories() {
 434      global $CFG;
 435  
 436      $dbdirs = array();
 437  
 438      /// First, the main one (lib/db)
 439      $dbdirs[] = $CFG->libdir.'/db';
 440  
 441      /// Then, all the ones defined by core_component::get_plugin_types()
 442      $plugintypes = core_component::get_plugin_types();
 443      foreach ($plugintypes as $plugintype => $pluginbasedir) {
 444          if ($plugins = core_component::get_plugin_list($plugintype)) {
 445              foreach ($plugins as $plugin => $plugindir) {
 446                  $dbdirs[] = $plugindir.'/db';
 447              }
 448          }
 449      }
 450  
 451      return $dbdirs;
 452  }
 453  
 454  /**
 455   * Try to obtain or release the cron lock.
 456   * @param string  $name  name of lock
 457   * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionally
 458   * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
 459   * @return bool true if lock obtained
 460   */
 461  function set_cron_lock($name, $until, $ignorecurrent=false) {
 462      global $DB;
 463      if (empty($name)) {
 464          debugging("Tried to get a cron lock for a null fieldname");
 465          return false;
 466      }
 467  
 468      // remove lock by force == remove from config table
 469      if (is_null($until)) {
 470          set_config($name, null);
 471          return true;
 472      }
 473  
 474      if (!$ignorecurrent) {
 475          // read value from db - other processes might have changed it
 476          $value = $DB->get_field('config', 'value', array('name'=>$name));
 477  
 478          if ($value and $value > time()) {
 479              //lock active
 480              return false;
 481          }
 482      }
 483  
 484      set_config($name, $until);
 485      return true;
 486  }
 487  
 488  /**
 489   * Test if and critical warnings are present
 490   * @return bool
 491   */
 492  function admin_critical_warnings_present() {
 493      global $SESSION;
 494  
 495      if (!has_capability('moodle/site:config', context_system::instance())) {
 496          return 0;
 497      }
 498  
 499      if (!isset($SESSION->admin_critical_warning)) {
 500          $SESSION->admin_critical_warning = 0;
 501          if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
 502              $SESSION->admin_critical_warning = 1;
 503          }
 504      }
 505  
 506      return $SESSION->admin_critical_warning;
 507  }
 508  
 509  /**
 510   * Detects if float supports at least 10 decimal digits
 511   *
 512   * Detects if float supports at least 10 decimal digits
 513   * and also if float-->string conversion works as expected.
 514   *
 515   * @return bool true if problem found
 516   */
 517  function is_float_problem() {
 518      $num1 = 2009010200.01;
 519      $num2 = 2009010200.02;
 520  
 521      return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
 522  }
 523  
 524  /**
 525   * Try to verify that dataroot is not accessible from web.
 526   *
 527   * Try to verify that dataroot is not accessible from web.
 528   * It is not 100% correct but might help to reduce number of vulnerable sites.
 529   * Protection from httpd.conf and .htaccess is not detected properly.
 530   *
 531   * @uses INSECURE_DATAROOT_WARNING
 532   * @uses INSECURE_DATAROOT_ERROR
 533   * @param bool $fetchtest try to test public access by fetching file, default false
 534   * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
 535   */
 536  function is_dataroot_insecure($fetchtest=false) {
 537      global $CFG;
 538  
 539      $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
 540  
 541      $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
 542      $rp = strrev(trim($rp, '/'));
 543      $rp = explode('/', $rp);
 544      foreach($rp as $r) {
 545          if (strpos($siteroot, '/'.$r.'/') === 0) {
 546              $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
 547          } else {
 548              break; // probably alias root
 549          }
 550      }
 551  
 552      $siteroot = strrev($siteroot);
 553      $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
 554  
 555      if (strpos($dataroot, $siteroot) !== 0) {
 556          return false;
 557      }
 558  
 559      if (!$fetchtest) {
 560          return INSECURE_DATAROOT_WARNING;
 561      }
 562  
 563      // now try all methods to fetch a test file using http protocol
 564  
 565      $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
 566      preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
 567      $httpdocroot = $matches[1];
 568      $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
 569      make_upload_directory('diag');
 570      $testfile = $CFG->dataroot.'/diag/public.txt';
 571      if (!file_exists($testfile)) {
 572          file_put_contents($testfile, 'test file, do not delete');
 573          @chmod($testfile, $CFG->filepermissions);
 574      }
 575      $teststr = trim(file_get_contents($testfile));
 576      if (empty($teststr)) {
 577      // hmm, strange
 578          return INSECURE_DATAROOT_WARNING;
 579      }
 580  
 581      $testurl = $datarooturl.'/diag/public.txt';
 582      if (extension_loaded('curl') and
 583          !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
 584          !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
 585          ($ch = @curl_init($testurl)) !== false) {
 586          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 587          curl_setopt($ch, CURLOPT_HEADER, false);
 588          $data = curl_exec($ch);
 589          if (!curl_errno($ch)) {
 590              $data = trim($data);
 591              if ($data === $teststr) {
 592                  curl_close($ch);
 593                  return INSECURE_DATAROOT_ERROR;
 594              }
 595          }
 596          curl_close($ch);
 597      }
 598  
 599      if ($data = @file_get_contents($testurl)) {
 600          $data = trim($data);
 601          if ($data === $teststr) {
 602              return INSECURE_DATAROOT_ERROR;
 603          }
 604      }
 605  
 606      preg_match('|https?://([^/]+)|i', $testurl, $matches);
 607      $sitename = $matches[1];
 608      $error = 0;
 609      if ($fp = @fsockopen($sitename, 80, $error)) {
 610          preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
 611          $localurl = $matches[1];
 612          $out = "GET $localurl HTTP/1.1\r\n";
 613          $out .= "Host: $sitename\r\n";
 614          $out .= "Connection: Close\r\n\r\n";
 615          fwrite($fp, $out);
 616          $data = '';
 617          $incoming = false;
 618          while (!feof($fp)) {
 619              if ($incoming) {
 620                  $data .= fgets($fp, 1024);
 621              } else if (@fgets($fp, 1024) === "\r\n") {
 622                      $incoming = true;
 623                  }
 624          }
 625          fclose($fp);
 626          $data = trim($data);
 627          if ($data === $teststr) {
 628              return INSECURE_DATAROOT_ERROR;
 629          }
 630      }
 631  
 632      return INSECURE_DATAROOT_WARNING;
 633  }
 634  
 635  /**
 636   * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
 637   */
 638  function enable_cli_maintenance_mode() {
 639      global $CFG, $SITE;
 640  
 641      if (file_exists("$CFG->dataroot/climaintenance.html")) {
 642          unlink("$CFG->dataroot/climaintenance.html");
 643      }
 644  
 645      if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
 646          $data = $CFG->maintenance_message;
 647          $data = bootstrap_renderer::early_error_content($data, null, null, null);
 648          $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
 649  
 650      } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
 651          $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
 652  
 653      } else {
 654          $data = get_string('sitemaintenance', 'admin');
 655          $data = bootstrap_renderer::early_error_content($data, null, null, null);
 656          $data = bootstrap_renderer::plain_page(get_string('sitemaintenancetitle', 'admin',
 657              format_string($SITE->fullname, true, ['context' => context_system::instance()])), $data);
 658      }
 659  
 660      file_put_contents("$CFG->dataroot/climaintenance.html", $data);
 661      chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
 662  }
 663  
 664  /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
 665  
 666  
 667  /**
 668   * Interface for anything appearing in the admin tree
 669   *
 670   * The interface that is implemented by anything that appears in the admin tree
 671   * block. It forces inheriting classes to define a method for checking user permissions
 672   * and methods for finding something in the admin tree.
 673   *
 674   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 675   */
 676  interface part_of_admin_tree {
 677  
 678  /**
 679   * Finds a named part_of_admin_tree.
 680   *
 681   * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
 682   * and not parentable_part_of_admin_tree, then this function should only check if
 683   * $this->name matches $name. If it does, it should return a reference to $this,
 684   * otherwise, it should return a reference to NULL.
 685   *
 686   * If a class inherits parentable_part_of_admin_tree, this method should be called
 687   * recursively on all child objects (assuming, of course, the parent object's name
 688   * doesn't match the search criterion).
 689   *
 690   * @param string $name The internal name of the part_of_admin_tree we're searching for.
 691   * @return mixed An object reference or a NULL reference.
 692   */
 693      public function locate($name);
 694  
 695      /**
 696       * Removes named part_of_admin_tree.
 697       *
 698       * @param string $name The internal name of the part_of_admin_tree we want to remove.
 699       * @return bool success.
 700       */
 701      public function prune($name);
 702  
 703      /**
 704       * Search using query
 705       * @param string $query
 706       * @return mixed array-object structure of found settings and pages
 707       */
 708      public function search($query);
 709  
 710      /**
 711       * Verifies current user's access to this part_of_admin_tree.
 712       *
 713       * Used to check if the current user has access to this part of the admin tree or
 714       * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
 715       * then this method is usually just a call to has_capability() in the site context.
 716       *
 717       * If a class inherits parentable_part_of_admin_tree, this method should return the
 718       * logical OR of the return of check_access() on all child objects.
 719       *
 720       * @return bool True if the user has access, false if she doesn't.
 721       */
 722      public function check_access();
 723  
 724      /**
 725       * Mostly useful for removing of some parts of the tree in admin tree block.
 726       *
 727       * @return True is hidden from normal list view
 728       */
 729      public function is_hidden();
 730  
 731      /**
 732       * Show we display Save button at the page bottom?
 733       * @return bool
 734       */
 735      public function show_save();
 736  }
 737  
 738  
 739  /**
 740   * Interface implemented by any part_of_admin_tree that has children.
 741   *
 742   * The interface implemented by any part_of_admin_tree that can be a parent
 743   * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
 744   * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
 745   * include an add method for adding other part_of_admin_tree objects as children.
 746   *
 747   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 748   */
 749  interface parentable_part_of_admin_tree extends part_of_admin_tree {
 750  
 751  /**
 752   * Adds a part_of_admin_tree object to the admin tree.
 753   *
 754   * Used to add a part_of_admin_tree object to this object or a child of this
 755   * object. $something should only be added if $destinationname matches
 756   * $this->name. If it doesn't, add should be called on child objects that are
 757   * also parentable_part_of_admin_tree's.
 758   *
 759   * $something should be appended as the last child in the $destinationname. If the
 760   * $beforesibling is specified, $something should be prepended to it. If the given
 761   * sibling is not found, $something should be appended to the end of $destinationname
 762   * and a developer debugging message should be displayed.
 763   *
 764   * @param string $destinationname The internal name of the new parent for $something.
 765   * @param part_of_admin_tree $something The object to be added.
 766   * @return bool True on success, false on failure.
 767   */
 768      public function add($destinationname, $something, $beforesibling = null);
 769  
 770  }
 771  
 772  
 773  /**
 774   * The object used to represent folders (a.k.a. categories) in the admin tree block.
 775   *
 776   * Each admin_category object contains a number of part_of_admin_tree objects.
 777   *
 778   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 779   */
 780  class admin_category implements parentable_part_of_admin_tree, linkable_settings_page {
 781  
 782      /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */
 783      protected $children;
 784      /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
 785      public $name;
 786      /** @var string The displayed name for this category. Usually obtained through get_string() */
 787      public $visiblename;
 788      /** @var bool Should this category be hidden in admin tree block? */
 789      public $hidden;
 790      /** @var mixed Either a string or an array or strings */
 791      public $path;
 792      /** @var mixed Either a string or an array or strings */
 793      public $visiblepath;
 794  
 795      /** @var array fast lookup category cache, all categories of one tree point to one cache */
 796      protected $category_cache;
 797  
 798      /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */
 799      protected $sort = false;
 800      /** @var bool If set to true children will be sorted in ascending order. */
 801      protected $sortasc = true;
 802      /** @var bool If set to true sub categories and pages will be split and then sorted.. */
 803      protected $sortsplit = true;
 804      /** @var bool $sorted True if the children have been sorted and don't need resorting */
 805      protected $sorted = false;
 806  
 807      /**
 808       * Constructor for an empty admin category
 809       *
 810       * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
 811       * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
 812       * @param bool $hidden hide category in admin tree block, defaults to false
 813       */
 814      public function __construct($name, $visiblename, $hidden=false) {
 815          $this->children    = array();
 816          $this->name        = $name;
 817          $this->visiblename = $visiblename;
 818          $this->hidden      = $hidden;
 819      }
 820  
 821      /**
 822       * Get the URL to view this settings page.
 823       *
 824       * @return moodle_url
 825       */
 826      public function get_settings_page_url(): moodle_url {
 827          return new moodle_url(
 828              '/admin/category.php',
 829              [
 830                  'category' => $this->name,
 831              ]
 832          );
 833      }
 834  
 835      /**
 836       * Returns a reference to the part_of_admin_tree object with internal name $name.
 837       *
 838       * @param string $name The internal name of the object we want.
 839       * @param bool $findpath initialize path and visiblepath arrays
 840       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
 841       *                  defaults to false
 842       */
 843      public function locate($name, $findpath=false) {
 844          if (!isset($this->category_cache[$this->name])) {
 845              // somebody much have purged the cache
 846              $this->category_cache[$this->name] = $this;
 847          }
 848  
 849          if ($this->name == $name) {
 850              if ($findpath) {
 851                  $this->visiblepath[] = $this->visiblename;
 852                  $this->path[]        = $this->name;
 853              }
 854              return $this;
 855          }
 856  
 857          // quick category lookup
 858          if (!$findpath and isset($this->category_cache[$name])) {
 859              return $this->category_cache[$name];
 860          }
 861  
 862          $return = NULL;
 863          foreach($this->children as $childid=>$unused) {
 864              if ($return = $this->children[$childid]->locate($name, $findpath)) {
 865                  break;
 866              }
 867          }
 868  
 869          if (!is_null($return) and $findpath) {
 870              $return->visiblepath[] = $this->visiblename;
 871              $return->path[]        = $this->name;
 872          }
 873  
 874          return $return;
 875      }
 876  
 877      /**
 878       * Search using query
 879       *
 880       * @param string query
 881       * @return mixed array-object structure of found settings and pages
 882       */
 883      public function search($query) {
 884          $result = array();
 885          foreach ($this->get_children() as $child) {
 886              $subsearch = $child->search($query);
 887              if (!is_array($subsearch)) {
 888                  debugging('Incorrect search result from '.$child->name);
 889                  continue;
 890              }
 891              $result = array_merge($result, $subsearch);
 892          }
 893          return $result;
 894      }
 895  
 896      /**
 897       * Removes part_of_admin_tree object with internal name $name.
 898       *
 899       * @param string $name The internal name of the object we want to remove.
 900       * @return bool success
 901       */
 902      public function prune($name) {
 903  
 904          if ($this->name == $name) {
 905              return false;  //can not remove itself
 906          }
 907  
 908          foreach($this->children as $precedence => $child) {
 909              if ($child->name == $name) {
 910                  // clear cache and delete self
 911                  while($this->category_cache) {
 912                      // delete the cache, but keep the original array address
 913                      array_pop($this->category_cache);
 914                  }
 915                  unset($this->children[$precedence]);
 916                  return true;
 917              } else if ($this->children[$precedence]->prune($name)) {
 918                  return true;
 919              }
 920          }
 921          return false;
 922      }
 923  
 924      /**
 925       * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
 926       *
 927       * By default the new part of the tree is appended as the last child of the parent. You
 928       * can specify a sibling node that the new part should be prepended to. If the given
 929       * sibling is not found, the part is appended to the end (as it would be by default) and
 930       * a developer debugging message is displayed.
 931       *
 932       * @throws coding_exception if the $beforesibling is empty string or is not string at all.
 933       * @param string $destinationame The internal name of the immediate parent that we want for $something.
 934       * @param mixed $something A part_of_admin_tree or setting instance to be added.
 935       * @param string $beforesibling The name of the parent's child the $something should be prepended to.
 936       * @return bool True if successfully added, false if $something can not be added.
 937       */
 938      public function add($parentname, $something, $beforesibling = null) {
 939          global $CFG;
 940  
 941          $parent = $this->locate($parentname);
 942          if (is_null($parent)) {
 943              debugging('parent does not exist!');
 944              return false;
 945          }
 946  
 947          if ($something instanceof part_of_admin_tree) {
 948              if (!($parent instanceof parentable_part_of_admin_tree)) {
 949                  debugging('error - parts of tree can be inserted only into parentable parts');
 950                  return false;
 951              }
 952              if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
 953                  // The name of the node is already used, simply warn the developer that this should not happen.
 954                  // It is intentional to check for the debug level before performing the check.
 955                  debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
 956              }
 957              if (is_null($beforesibling)) {
 958                  // Append $something as the parent's last child.
 959                  $parent->children[] = $something;
 960              } else {
 961                  if (!is_string($beforesibling) or trim($beforesibling) === '') {
 962                      throw new coding_exception('Unexpected value of the beforesibling parameter');
 963                  }
 964                  // Try to find the position of the sibling.
 965                  $siblingposition = null;
 966                  foreach ($parent->children as $childposition => $child) {
 967                      if ($child->name === $beforesibling) {
 968                          $siblingposition = $childposition;
 969                          break;
 970                      }
 971                  }
 972                  if (is_null($siblingposition)) {
 973                      debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
 974                      $parent->children[] = $something;
 975                  } else {
 976                      $parent->children = array_merge(
 977                          array_slice($parent->children, 0, $siblingposition),
 978                          array($something),
 979                          array_slice($parent->children, $siblingposition)
 980                      );
 981                  }
 982              }
 983              if ($something instanceof admin_category) {
 984                  if (isset($this->category_cache[$something->name])) {
 985                      debugging('Duplicate admin category name: '.$something->name);
 986                  } else {
 987                      $this->category_cache[$something->name] = $something;
 988                      $something->category_cache =& $this->category_cache;
 989                      foreach ($something->children as $child) {
 990                          // just in case somebody already added subcategories
 991                          if ($child instanceof admin_category) {
 992                              if (isset($this->category_cache[$child->name])) {
 993                                  debugging('Duplicate admin category name: '.$child->name);
 994                              } else {
 995                                  $this->category_cache[$child->name] = $child;
 996                                  $child->category_cache =& $this->category_cache;
 997                              }
 998                          }
 999                      }
1000                  }
1001              }
1002              return true;
1003  
1004          } else {
1005              debugging('error - can not add this element');
1006              return false;
1007          }
1008  
1009      }
1010  
1011      /**
1012       * Checks if the user has access to anything in this category.
1013       *
1014       * @return bool True if the user has access to at least one child in this category, false otherwise.
1015       */
1016      public function check_access() {
1017          foreach ($this->children as $child) {
1018              if ($child->check_access()) {
1019                  return true;
1020              }
1021          }
1022          return false;
1023      }
1024  
1025      /**
1026       * Is this category hidden in admin tree block?
1027       *
1028       * @return bool True if hidden
1029       */
1030      public function is_hidden() {
1031          return $this->hidden;
1032      }
1033  
1034      /**
1035       * Show we display Save button at the page bottom?
1036       * @return bool
1037       */
1038      public function show_save() {
1039          foreach ($this->children as $child) {
1040              if ($child->show_save()) {
1041                  return true;
1042              }
1043          }
1044          return false;
1045      }
1046  
1047      /**
1048       * Sets sorting on this category.
1049       *
1050       * Please note this function doesn't actually do the sorting.
1051       * It can be called anytime.
1052       * Sorting occurs when the user calls get_children.
1053       * Code using the children array directly won't see the sorted results.
1054       *
1055       * @param bool $sort If set to true children will be sorted, if false they won't be.
1056       * @param bool $asc If true sorting will be ascending, otherwise descending.
1057       * @param bool $split If true we sort pages and sub categories separately.
1058       */
1059      public function set_sorting($sort, $asc = true, $split = true) {
1060          $this->sort = (bool)$sort;
1061          $this->sortasc = (bool)$asc;
1062          $this->sortsplit = (bool)$split;
1063      }
1064  
1065      /**
1066       * Returns the children associated with this category.
1067       *
1068       * @return part_of_admin_tree[]
1069       */
1070      public function get_children() {
1071          // If we should sort and it hasn't already been sorted.
1072          if ($this->sort && !$this->sorted) {
1073              if ($this->sortsplit) {
1074                  $categories = array();
1075                  $pages = array();
1076                  foreach ($this->children as $child) {
1077                      if ($child instanceof admin_category) {
1078                          $categories[] = $child;
1079                      } else {
1080                          $pages[] = $child;
1081                      }
1082                  }
1083                  core_collator::asort_objects_by_property($categories, 'visiblename');
1084                  core_collator::asort_objects_by_property($pages, 'visiblename');
1085                  if (!$this->sortasc) {
1086                      $categories = array_reverse($categories);
1087                      $pages = array_reverse($pages);
1088                  }
1089                  $this->children = array_merge($pages, $categories);
1090              } else {
1091                  core_collator::asort_objects_by_property($this->children, 'visiblename');
1092                  if (!$this->sortasc) {
1093                      $this->children = array_reverse($this->children);
1094                  }
1095              }
1096              $this->sorted = true;
1097          }
1098          return $this->children;
1099      }
1100  
1101      /**
1102       * Magically gets a property from this object.
1103       *
1104       * @param $property
1105       * @return part_of_admin_tree[]
1106       * @throws coding_exception
1107       */
1108      public function __get($property) {
1109          if ($property === 'children') {
1110              return $this->get_children();
1111          }
1112          throw new coding_exception('Invalid property requested.');
1113      }
1114  
1115      /**
1116       * Magically sets a property against this object.
1117       *
1118       * @param string $property
1119       * @param mixed $value
1120       * @throws coding_exception
1121       */
1122      public function __set($property, $value) {
1123          if ($property === 'children') {
1124              $this->sorted = false;
1125              $this->children = $value;
1126          } else {
1127              throw new coding_exception('Invalid property requested.');
1128          }
1129      }
1130  
1131      /**
1132       * Checks if an inaccessible property is set.
1133       *
1134       * @param string $property
1135       * @return bool
1136       * @throws coding_exception
1137       */
1138      public function __isset($property) {
1139          if ($property === 'children') {
1140              return isset($this->children);
1141          }
1142          throw new coding_exception('Invalid property requested.');
1143      }
1144  }
1145  
1146  
1147  /**
1148   * Root of admin settings tree, does not have any parent.
1149   *
1150   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1151   */
1152  class admin_root extends admin_category {
1153  /** @var array List of errors */
1154      public $errors;
1155      /** @var string search query */
1156      public $search;
1157      /** @var bool full tree flag - true means all settings required, false only pages required */
1158      public $fulltree;
1159      /** @var bool flag indicating loaded tree */
1160      public $loaded;
1161      /** @var mixed site custom defaults overriding defaults in settings files*/
1162      public $custom_defaults;
1163  
1164      /**
1165       * @param bool $fulltree true means all settings required,
1166       *                            false only pages required
1167       */
1168      public function __construct($fulltree) {
1169          global $CFG;
1170  
1171          parent::__construct('root', get_string('administration'), false);
1172          $this->errors   = array();
1173          $this->search   = '';
1174          $this->fulltree = $fulltree;
1175          $this->loaded   = false;
1176  
1177          $this->category_cache = array();
1178  
1179          // load custom defaults if found
1180          $this->custom_defaults = null;
1181          $defaultsfile = "$CFG->dirroot/local/defaults.php";
1182          if (is_readable($defaultsfile)) {
1183              $defaults = array();
1184              include($defaultsfile);
1185              if (is_array($defaults) and count($defaults)) {
1186                  $this->custom_defaults = $defaults;
1187              }
1188          }
1189      }
1190  
1191      /**
1192       * Empties children array, and sets loaded to false
1193       *
1194       * @param bool $requirefulltree
1195       */
1196      public function purge_children($requirefulltree) {
1197          $this->children = array();
1198          $this->fulltree = ($requirefulltree || $this->fulltree);
1199          $this->loaded   = false;
1200          //break circular dependencies - this helps PHP 5.2
1201          while($this->category_cache) {
1202              array_pop($this->category_cache);
1203          }
1204          $this->category_cache = array();
1205      }
1206  }
1207  
1208  
1209  /**
1210   * Links external PHP pages into the admin tree.
1211   *
1212   * See detailed usage example at the top of this document (adminlib.php)
1213   *
1214   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1215   */
1216  class admin_externalpage implements part_of_admin_tree, linkable_settings_page {
1217  
1218      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1219      public $name;
1220  
1221      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1222      public $visiblename;
1223  
1224      /** @var string The external URL that we should link to when someone requests this external page. */
1225      public $url;
1226  
1227      /** @var array The role capability/permission a user must have to access this external page. */
1228      public $req_capability;
1229  
1230      /** @var object The context in which capability/permission should be checked, default is site context. */
1231      public $context;
1232  
1233      /** @var bool hidden in admin tree block. */
1234      public $hidden;
1235  
1236      /** @var mixed either string or array of string */
1237      public $path;
1238  
1239      /** @var array list of visible names of page parents */
1240      public $visiblepath;
1241  
1242      /**
1243       * Constructor for adding an external page into the admin tree.
1244       *
1245       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1246       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1247       * @param string $url The external URL that we should link to when someone requests this external page.
1248       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1249       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1250       * @param stdClass $context The context the page relates to. Not sure what happens
1251       *      if you specify something other than system or front page. Defaults to system.
1252       */
1253      public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1254          $this->name        = $name;
1255          $this->visiblename = $visiblename;
1256          $this->url         = $url;
1257          if (is_array($req_capability)) {
1258              $this->req_capability = $req_capability;
1259          } else {
1260              $this->req_capability = array($req_capability);
1261          }
1262          $this->hidden = $hidden;
1263          $this->context = $context;
1264      }
1265  
1266      /**
1267       * Get the URL to view this settings page.
1268       *
1269       * @return moodle_url
1270       */
1271      public function get_settings_page_url(): moodle_url {
1272          return new moodle_url($this->url);
1273      }
1274  
1275      /**
1276       * Returns a reference to the part_of_admin_tree object with internal name $name.
1277       *
1278       * @param string $name The internal name of the object we want.
1279       * @param bool $findpath defaults to false
1280       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1281       */
1282      public function locate($name, $findpath=false) {
1283          if ($this->name == $name) {
1284              if ($findpath) {
1285                  $this->visiblepath = array($this->visiblename);
1286                  $this->path        = array($this->name);
1287              }
1288              return $this;
1289          } else {
1290              $return = NULL;
1291              return $return;
1292          }
1293      }
1294  
1295      /**
1296       * This function always returns false, required function by interface
1297       *
1298       * @param string $name
1299       * @return false
1300       */
1301      public function prune($name) {
1302          return false;
1303      }
1304  
1305      /**
1306       * Search using query
1307       *
1308       * @param string $query
1309       * @return mixed array-object structure of found settings and pages
1310       */
1311      public function search($query) {
1312          $found = false;
1313          if (strpos(strtolower($this->name), $query) !== false) {
1314              $found = true;
1315          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1316                  $found = true;
1317              }
1318          if ($found) {
1319              $result = new stdClass();
1320              $result->page     = $this;
1321              $result->settings = array();
1322              return array($this->name => $result);
1323          } else {
1324              return array();
1325          }
1326      }
1327  
1328      /**
1329       * Determines if the current user has access to this external page based on $this->req_capability.
1330       *
1331       * @return bool True if user has access, false otherwise.
1332       */
1333      public function check_access() {
1334          global $CFG;
1335          $context = empty($this->context) ? context_system::instance() : $this->context;
1336          foreach($this->req_capability as $cap) {
1337              if (has_capability($cap, $context)) {
1338                  return true;
1339              }
1340          }
1341          return false;
1342      }
1343  
1344      /**
1345       * Is this external page hidden in admin tree block?
1346       *
1347       * @return bool True if hidden
1348       */
1349      public function is_hidden() {
1350          return $this->hidden;
1351      }
1352  
1353      /**
1354       * Show we display Save button at the page bottom?
1355       * @return bool
1356       */
1357      public function show_save() {
1358          return false;
1359      }
1360  }
1361  
1362  /**
1363   * Used to store details of the dependency between two settings elements.
1364   *
1365   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1366   * @copyright 2017 Davo Smith, Synergy Learning
1367   */
1368  class admin_settingdependency {
1369      /** @var string the name of the setting to be shown/hidden */
1370      public $settingname;
1371      /** @var string the setting this is dependent on */
1372      public $dependenton;
1373      /** @var string the condition to show/hide the element */
1374      public $condition;
1375      /** @var string the value to compare against */
1376      public $value;
1377  
1378      /** @var string[] list of valid conditions */
1379      private static $validconditions = ['checked', 'notchecked', 'noitemselected', 'eq', 'neq', 'in'];
1380  
1381      /**
1382       * admin_settingdependency constructor.
1383       * @param string $settingname
1384       * @param string $dependenton
1385       * @param string $condition
1386       * @param string $value
1387       * @throws \coding_exception
1388       */
1389      public function __construct($settingname, $dependenton, $condition, $value) {
1390          $this->settingname = $this->parse_name($settingname);
1391          $this->dependenton = $this->parse_name($dependenton);
1392          $this->condition = $condition;
1393          $this->value = $value;
1394  
1395          if (!in_array($this->condition, self::$validconditions)) {
1396              throw new coding_exception("Invalid condition '$condition'");
1397          }
1398      }
1399  
1400      /**
1401       * Convert the setting name into the form field name.
1402       * @param string $name
1403       * @return string
1404       */
1405      private function parse_name($name) {
1406          $bits = explode('/', $name);
1407          $name = array_pop($bits);
1408          $plugin = '';
1409          if ($bits) {
1410              $plugin = array_pop($bits);
1411              if ($plugin === 'moodle') {
1412                  $plugin = '';
1413              }
1414          }
1415          return 's_'.$plugin.'_'.$name;
1416      }
1417  
1418      /**
1419       * Gather together all the dependencies in a format suitable for initialising javascript
1420       * @param admin_settingdependency[] $dependencies
1421       * @return array
1422       */
1423      public static function prepare_for_javascript($dependencies) {
1424          $result = [];
1425          foreach ($dependencies as $d) {
1426              if (!isset($result[$d->dependenton])) {
1427                  $result[$d->dependenton] = [];
1428              }
1429              if (!isset($result[$d->dependenton][$d->condition])) {
1430                  $result[$d->dependenton][$d->condition] = [];
1431              }
1432              if (!isset($result[$d->dependenton][$d->condition][$d->value])) {
1433                  $result[$d->dependenton][$d->condition][$d->value] = [];
1434              }
1435              $result[$d->dependenton][$d->condition][$d->value][] = $d->settingname;
1436          }
1437          return $result;
1438      }
1439  }
1440  
1441  /**
1442   * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1443   *
1444   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1445   */
1446  class admin_settingpage implements part_of_admin_tree, linkable_settings_page {
1447  
1448      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1449      public $name;
1450  
1451      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1452      public $visiblename;
1453  
1454      /** @var mixed An array of admin_setting objects that are part of this setting page. */
1455      public $settings;
1456  
1457      /** @var admin_settingdependency[] list of settings to hide when certain conditions are met */
1458      protected $dependencies = [];
1459  
1460      /** @var array The role capability/permission a user must have to access this external page. */
1461      public $req_capability;
1462  
1463      /** @var object The context in which capability/permission should be checked, default is site context. */
1464      public $context;
1465  
1466      /** @var bool hidden in admin tree block. */
1467      public $hidden;
1468  
1469      /** @var mixed string of paths or array of strings of paths */
1470      public $path;
1471  
1472      /** @var array list of visible names of page parents */
1473      public $visiblepath;
1474  
1475      /**
1476       * see admin_settingpage for details of this function
1477       *
1478       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1479       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1480       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1481       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1482       * @param stdClass $context The context the page relates to. Not sure what happens
1483       *      if you specify something other than system or front page. Defaults to system.
1484       */
1485      public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1486          $this->settings    = new stdClass();
1487          $this->name        = $name;
1488          $this->visiblename = $visiblename;
1489          if (is_array($req_capability)) {
1490              $this->req_capability = $req_capability;
1491          } else {
1492              $this->req_capability = array($req_capability);
1493          }
1494          $this->hidden      = $hidden;
1495          $this->context     = $context;
1496      }
1497  
1498      /**
1499       * Get the URL to view this page.
1500       *
1501       * @return moodle_url
1502       */
1503      public function get_settings_page_url(): moodle_url {
1504          return new moodle_url(
1505              '/admin/settings.php',
1506              [
1507                  'section' => $this->name,
1508              ]
1509          );
1510      }
1511  
1512      /**
1513       * see admin_category
1514       *
1515       * @param string $name
1516       * @param bool $findpath
1517       * @return mixed Object (this) if name ==  this->name, else returns null
1518       */
1519      public function locate($name, $findpath=false) {
1520          if ($this->name == $name) {
1521              if ($findpath) {
1522                  $this->visiblepath = array($this->visiblename);
1523                  $this->path        = array($this->name);
1524              }
1525              return $this;
1526          } else {
1527              $return = NULL;
1528              return $return;
1529          }
1530      }
1531  
1532      /**
1533       * Search string in settings page.
1534       *
1535       * @param string $query
1536       * @return array
1537       */
1538      public function search($query) {
1539          $found = array();
1540  
1541          foreach ($this->settings as $setting) {
1542              if ($setting->is_related($query)) {
1543                  $found[] = $setting;
1544              }
1545          }
1546  
1547          if ($found) {
1548              $result = new stdClass();
1549              $result->page     = $this;
1550              $result->settings = $found;
1551              return array($this->name => $result);
1552          }
1553  
1554          $found = false;
1555          if (strpos(strtolower($this->name), $query) !== false) {
1556              $found = true;
1557          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1558                  $found = true;
1559              }
1560          if ($found) {
1561              $result = new stdClass();
1562              $result->page     = $this;
1563              $result->settings = array();
1564              return array($this->name => $result);
1565          } else {
1566              return array();
1567          }
1568      }
1569  
1570      /**
1571       * This function always returns false, required by interface
1572       *
1573       * @param string $name
1574       * @return bool Always false
1575       */
1576      public function prune($name) {
1577          return false;
1578      }
1579  
1580      /**
1581       * adds an admin_setting to this admin_settingpage
1582       *
1583       * 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
1584       * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1585       *
1586       * @param object $setting is the admin_setting object you want to add
1587       * @return bool true if successful, false if not
1588       */
1589      public function add($setting) {
1590          if (!($setting instanceof admin_setting)) {
1591              debugging('error - not a setting instance');
1592              return false;
1593          }
1594  
1595          $name = $setting->name;
1596          if ($setting->plugin) {
1597              $name = $setting->plugin . $name;
1598          }
1599          $this->settings->{$name} = $setting;
1600          return true;
1601      }
1602  
1603      /**
1604       * Hide the named setting if the specified condition is matched.
1605       *
1606       * @param string $settingname
1607       * @param string $dependenton
1608       * @param string $condition
1609       * @param string $value
1610       */
1611      public function hide_if($settingname, $dependenton, $condition = 'notchecked', $value = '1') {
1612          $this->dependencies[] = new admin_settingdependency($settingname, $dependenton, $condition, $value);
1613  
1614          // Reformat the dependency name to the plugin | name format used in the display.
1615          $dependenton = str_replace('/', ' | ', $dependenton);
1616  
1617          // Let the setting know, so it can be displayed underneath.
1618          $findname = str_replace('/', '', $settingname);
1619          foreach ($this->settings as $name => $setting) {
1620              if ($name === $findname) {
1621                  $setting->add_dependent_on($dependenton);
1622              }
1623          }
1624      }
1625  
1626      /**
1627       * see admin_externalpage
1628       *
1629       * @return bool Returns true for yes false for no
1630       */
1631      public function check_access() {
1632          global $CFG;
1633          $context = empty($this->context) ? context_system::instance() : $this->context;
1634          foreach($this->req_capability as $cap) {
1635              if (has_capability($cap, $context)) {
1636                  return true;
1637              }
1638          }
1639          return false;
1640      }
1641  
1642      /**
1643       * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1644       * @return string Returns an XHTML string
1645       */
1646      public function output_html() {
1647          $adminroot = admin_get_root();
1648          $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1649          foreach($this->settings as $setting) {
1650              $fullname = $setting->get_full_name();
1651              if (array_key_exists($fullname, $adminroot->errors)) {
1652                  $data = $adminroot->errors[$fullname]->data;
1653              } else {
1654                  $data = $setting->get_setting();
1655                  // do not use defaults if settings not available - upgrade settings handles the defaults!
1656              }
1657              $return .= $setting->output_html($data);
1658          }
1659          $return .= '</fieldset>';
1660          return $return;
1661      }
1662  
1663      /**
1664       * Is this settings page hidden in admin tree block?
1665       *
1666       * @return bool True if hidden
1667       */
1668      public function is_hidden() {
1669          return $this->hidden;
1670      }
1671  
1672      /**
1673       * Show we display Save button at the page bottom?
1674       * @return bool
1675       */
1676      public function show_save() {
1677          foreach($this->settings as $setting) {
1678              if (empty($setting->nosave)) {
1679                  return true;
1680              }
1681          }
1682          return false;
1683      }
1684  
1685      /**
1686       * Should any of the settings on this page be shown / hidden based on conditions?
1687       * @return bool
1688       */
1689      public function has_dependencies() {
1690          return (bool)$this->dependencies;
1691      }
1692  
1693      /**
1694       * Format the setting show/hide conditions ready to initialise the page javascript
1695       * @return array
1696       */
1697      public function get_dependencies_for_javascript() {
1698          if (!$this->has_dependencies()) {
1699              return [];
1700          }
1701          return admin_settingdependency::prepare_for_javascript($this->dependencies);
1702      }
1703  }
1704  
1705  
1706  /**
1707   * Admin settings class. Only exists on setting pages.
1708   * Read & write happens at this level; no authentication.
1709   *
1710   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1711   */
1712  abstract class admin_setting {
1713      /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1714      public $name;
1715      /** @var string localised name */
1716      public $visiblename;
1717      /** @var string localised long description in Markdown format */
1718      public $description;
1719      /** @var mixed Can be string or array of string */
1720      public $defaultsetting;
1721      /** @var string */
1722      public $updatedcallback;
1723      /** @var mixed can be String or Null.  Null means main config table */
1724      public $plugin; // null means main config table
1725      /** @var bool true indicates this setting does not actually save anything, just information */
1726      public $nosave = false;
1727      /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1728      public $affectsmodinfo = false;
1729      /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */
1730      private $flags = array();
1731      /** @var bool Whether this field must be forced LTR. */
1732      private $forceltr = null;
1733      /** @var array list of other settings that may cause this setting to be hidden */
1734      private $dependenton = [];
1735      /** @var bool Whether this setting uses a custom form control */
1736      protected $customcontrol = false;
1737  
1738      /**
1739       * Constructor
1740       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1741       *                     or 'myplugin/mysetting' for ones in config_plugins.
1742       * @param string $visiblename localised name
1743       * @param string $description localised long description
1744       * @param mixed $defaultsetting string or array depending on implementation
1745       */
1746      public function __construct($name, $visiblename, $description, $defaultsetting) {
1747          $this->parse_setting_name($name);
1748          $this->visiblename    = $visiblename;
1749          $this->description    = $description;
1750          $this->defaultsetting = $defaultsetting;
1751      }
1752  
1753      /**
1754       * Generic function to add a flag to this admin setting.
1755       *
1756       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1757       * @param bool $default - The default for the flag
1758       * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name.
1759       * @param string $displayname - The display name for this flag. Used as a label next to the checkbox.
1760       */
1761      protected function set_flag_options($enabled, $default, $shortname, $displayname) {
1762          if (empty($this->flags[$shortname])) {
1763              $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname);
1764          } else {
1765              $this->flags[$shortname]->set_options($enabled, $default);
1766          }
1767      }
1768  
1769      /**
1770       * Set the enabled options flag on this admin setting.
1771       *
1772       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1773       * @param bool $default - The default for the flag
1774       */
1775      public function set_enabled_flag_options($enabled, $default) {
1776          $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin'));
1777      }
1778  
1779      /**
1780       * Set the advanced options flag on this admin setting.
1781       *
1782       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1783       * @param bool $default - The default for the flag
1784       */
1785      public function set_advanced_flag_options($enabled, $default) {
1786          $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced'));
1787      }
1788  
1789  
1790      /**
1791       * Set the locked options flag on this admin setting.
1792       *
1793       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1794       * @param bool $default - The default for the flag
1795       */
1796      public function set_locked_flag_options($enabled, $default) {
1797          $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
1798      }
1799  
1800      /**
1801       * Set the required options flag on this admin setting.
1802       *
1803       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED.
1804       * @param bool $default - The default for the flag.
1805       */
1806      public function set_required_flag_options($enabled, $default) {
1807          $this->set_flag_options($enabled, $default, 'required', new lang_string('required', 'core_admin'));
1808      }
1809  
1810      /**
1811       * Is this option forced in config.php?
1812       *
1813       * @return bool
1814       */
1815      public function is_readonly(): bool {
1816          global $CFG;
1817  
1818          if (empty($this->plugin)) {
1819              if (array_key_exists($this->name, $CFG->config_php_settings)) {
1820                  return true;
1821              }
1822          } else {
1823              if (array_key_exists($this->plugin, $CFG->forced_plugin_settings)
1824                  and array_key_exists($this->name, $CFG->forced_plugin_settings[$this->plugin])) {
1825                  return true;
1826              }
1827          }
1828          return false;
1829      }
1830  
1831      /**
1832       * Get the currently saved value for a setting flag
1833       *
1834       * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting.
1835       * @return bool
1836       */
1837      public function get_setting_flag_value(admin_setting_flag $flag) {
1838          $value = $this->config_read($this->name . '_' . $flag->get_shortname());
1839          if (!isset($value)) {
1840              $value = $flag->get_default();
1841          }
1842  
1843          return !empty($value);
1844      }
1845  
1846      /**
1847       * Get the list of defaults for the flags on this setting.
1848       *
1849       * @param array of strings describing the defaults for this setting. This is appended to by this function.
1850       */
1851      public function get_setting_flag_defaults(& $defaults) {
1852          foreach ($this->flags as $flag) {
1853              if ($flag->is_enabled() && $flag->get_default()) {
1854                  $defaults[] = $flag->get_displayname();
1855              }
1856          }
1857      }
1858  
1859      /**
1860       * Output the input fields for the advanced and locked flags on this setting.
1861       *
1862       * @param bool $adv - The current value of the advanced flag.
1863       * @param bool $locked - The current value of the locked flag.
1864       * @return string $output - The html for the flags.
1865       */
1866      public function output_setting_flags() {
1867          $output = '';
1868  
1869          foreach ($this->flags as $flag) {
1870              if ($flag->is_enabled()) {
1871                  $output .= $flag->output_setting_flag($this);
1872              }
1873          }
1874  
1875          if (!empty($output)) {
1876              return html_writer::tag('span', $output, array('class' => 'adminsettingsflags'));
1877          }
1878          return $output;
1879      }
1880  
1881      /**
1882       * Write the values of the flags for this admin setting.
1883       *
1884       * @param array $data - The data submitted from the form or null to set the default value for new installs.
1885       * @return bool - true if successful.
1886       */
1887      public function write_setting_flags($data) {
1888          $result = true;
1889          foreach ($this->flags as $flag) {
1890              $result = $result && $flag->write_setting_flag($this, $data);
1891          }
1892          return $result;
1893      }
1894  
1895      /**
1896       * Set up $this->name and potentially $this->plugin
1897       *
1898       * Set up $this->name and possibly $this->plugin based on whether $name looks
1899       * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1900       * on the names, that is, output a developer debug warning if the name
1901       * contains anything other than [a-zA-Z0-9_]+.
1902       *
1903       * @param string $name the setting name passed in to the constructor.
1904       */
1905      private function parse_setting_name($name) {
1906          $bits = explode('/', $name);
1907          if (count($bits) > 2) {
1908              throw new moodle_exception('invalidadminsettingname', '', '', $name);
1909          }
1910          $this->name = array_pop($bits);
1911          if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1912              throw new moodle_exception('invalidadminsettingname', '', '', $name);
1913          }
1914          if (!empty($bits)) {
1915              $this->plugin = array_pop($bits);
1916              if ($this->plugin === 'moodle') {
1917                  $this->plugin = null;
1918              } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1919                      throw new moodle_exception('invalidadminsettingname', '', '', $name);
1920                  }
1921          }
1922      }
1923  
1924      /**
1925       * Returns the fullname prefixed by the plugin
1926       * @return string
1927       */
1928      public function get_full_name() {
1929          return 's_'.$this->plugin.'_'.$this->name;
1930      }
1931  
1932      /**
1933       * Returns the ID string based on plugin and name
1934       * @return string
1935       */
1936      public function get_id() {
1937          return 'id_s_'.$this->plugin.'_'.$this->name;
1938      }
1939  
1940      /**
1941       * @param bool $affectsmodinfo If true, changes to this setting will
1942       *   cause the course cache to be rebuilt
1943       */
1944      public function set_affects_modinfo($affectsmodinfo) {
1945          $this->affectsmodinfo = $affectsmodinfo;
1946      }
1947  
1948      /**
1949       * Returns the config if possible
1950       *
1951       * @return mixed returns config if successful else null
1952       */
1953      public function config_read($name) {
1954          global $CFG;
1955          if (!empty($this->plugin)) {
1956              $value = get_config($this->plugin, $name);
1957              return $value === false ? NULL : $value;
1958  
1959          } else {
1960              if (isset($CFG->$name)) {
1961                  return $CFG->$name;
1962              } else {
1963                  return NULL;
1964              }
1965          }
1966      }
1967  
1968      /**
1969       * Used to set a config pair and log change
1970       *
1971       * @param string $name
1972       * @param mixed $value Gets converted to string if not null
1973       * @return bool Write setting to config table
1974       */
1975      public function config_write($name, $value) {
1976          global $DB, $USER, $CFG;
1977  
1978          if ($this->nosave) {
1979              return true;
1980          }
1981  
1982          // make sure it is a real change
1983          $oldvalue = get_config($this->plugin, $name);
1984          $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1985          $value = is_null($value) ? null : (string)$value;
1986  
1987          if ($oldvalue === $value) {
1988              return true;
1989          }
1990  
1991          // store change
1992          set_config($name, $value, $this->plugin);
1993  
1994          // Some admin settings affect course modinfo
1995          if ($this->affectsmodinfo) {
1996              // Clear course cache for all courses
1997              rebuild_course_cache(0, true);
1998          }
1999  
2000          $this->add_to_config_log($name, $oldvalue, $value);
2001  
2002          return true; // BC only
2003      }
2004  
2005      /**
2006       * Log config changes if necessary.
2007       * @param string $name
2008       * @param string $oldvalue
2009       * @param string $value
2010       */
2011      protected function add_to_config_log($name, $oldvalue, $value) {
2012          add_to_config_log($name, $oldvalue, $value, $this->plugin);
2013      }
2014  
2015      /**
2016       * Returns current value of this setting
2017       * @return mixed array or string depending on instance, NULL means not set yet
2018       */
2019      public abstract function get_setting();
2020  
2021      /**
2022       * Returns default setting if exists
2023       * @return mixed array or string depending on instance; NULL means no default, user must supply
2024       */
2025      public function get_defaultsetting() {
2026          $adminroot =  admin_get_root(false, false);
2027          if (!empty($adminroot->custom_defaults)) {
2028              $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
2029              if (isset($adminroot->custom_defaults[$plugin])) {
2030                  if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
2031                      return $adminroot->custom_defaults[$plugin][$this->name];
2032                  }
2033              }
2034          }
2035          return $this->defaultsetting;
2036      }
2037  
2038      /**
2039       * Store new setting
2040       *
2041       * @param mixed $data string or array, must not be NULL
2042       * @return string empty string if ok, string error message otherwise
2043       */
2044      public abstract function write_setting($data);
2045  
2046      /**
2047       * Return part of form with setting
2048       * This function should always be overwritten
2049       *
2050       * @param mixed $data array or string depending on setting
2051       * @param string $query
2052       * @return string
2053       */
2054      public function output_html($data, $query='') {
2055      // should be overridden
2056          return;
2057      }
2058  
2059      /**
2060       * Function called if setting updated - cleanup, cache reset, etc.
2061       * @param string $functionname Sets the function name
2062       * @return void
2063       */
2064      public function set_updatedcallback($functionname) {
2065          $this->updatedcallback = $functionname;
2066      }
2067  
2068      /**
2069       * Execute postupdatecallback if necessary.
2070       * @param mixed $original original value before write_setting()
2071       * @return bool true if changed, false if not.
2072       */
2073      public function post_write_settings($original) {
2074          // Comparison must work for arrays too.
2075          if (serialize($original) === serialize($this->get_setting())) {
2076              return false;
2077          }
2078  
2079          $callbackfunction = $this->updatedcallback;
2080          if (!empty($callbackfunction) and is_callable($callbackfunction)) {
2081              $callbackfunction($this->get_full_name());
2082          }
2083          return true;
2084      }
2085  
2086      /**
2087       * Is setting related to query text - used when searching
2088       * @param string $query
2089       * @return bool
2090       */
2091      public function is_related($query) {
2092          if (strpos(strtolower($this->name), $query) !== false) {
2093              return true;
2094          }
2095          if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
2096              return true;
2097          }
2098          if (strpos(core_text::strtolower($this->description), $query) !== false) {
2099              return true;
2100          }
2101          $current = $this->get_setting();
2102          if (!is_null($current)) {
2103              if (is_string($current)) {
2104                  if (strpos(core_text::strtolower($current), $query) !== false) {
2105                      return true;
2106                  }
2107              }
2108          }
2109          $default = $this->get_defaultsetting();
2110          if (!is_null($default)) {
2111              if (is_string($default)) {
2112                  if (strpos(core_text::strtolower($default), $query) !== false) {
2113                      return true;
2114                  }
2115              }
2116          }
2117          return false;
2118      }
2119  
2120      /**
2121       * Get whether this should be displayed in LTR mode.
2122       *
2123       * @return bool|null
2124       */
2125      public function get_force_ltr() {
2126          return $this->forceltr;
2127      }
2128  
2129      /**
2130       * Set whether to force LTR or not.
2131       *
2132       * @param bool $value True when forced, false when not force, null when unknown.
2133       */
2134      public function set_force_ltr($value) {
2135          $this->forceltr = $value;
2136      }
2137  
2138      /**
2139       * Add a setting to the list of those that could cause this one to be hidden
2140       * @param string $dependenton
2141       */
2142      public function add_dependent_on($dependenton) {
2143          $this->dependenton[] = $dependenton;
2144      }
2145  
2146      /**
2147       * Get a list of the settings that could cause this one to be hidden.
2148       * @return array
2149       */
2150      public function get_dependent_on() {
2151          return $this->dependenton;
2152      }
2153  
2154      /**
2155       * Whether this setting uses a custom form control.
2156       * This function is especially useful to decide if we should render a label element for this setting or not.
2157       *
2158       * @return bool
2159       */
2160      public function has_custom_form_control(): bool {
2161          return $this->customcontrol;
2162      }
2163  }
2164  
2165  /**
2166   * An additional option that can be applied to an admin setting.
2167   * The currently supported options are 'ADVANCED', 'LOCKED' and 'REQUIRED'.
2168   *
2169   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2170   */
2171  class admin_setting_flag {
2172      /** @var bool Flag to indicate if this option can be toggled for this setting */
2173      private $enabled = false;
2174      /** @var bool Flag to indicate if this option defaults to true or false */
2175      private $default = false;
2176      /** @var string Short string used to create setting name - e.g. 'adv' */
2177      private $shortname = '';
2178      /** @var string String used as the label for this flag */
2179      private $displayname = '';
2180      /** @const Checkbox for this flag is displayed in admin page */
2181      const ENABLED = true;
2182      /** @const Checkbox for this flag is not displayed in admin page */
2183      const DISABLED = false;
2184  
2185      /**
2186       * Constructor
2187       *
2188       * @param bool $enabled Can this option can be toggled.
2189       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2190       * @param bool $default The default checked state for this setting option.
2191       * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
2192       * @param string $displayname The displayname of this flag. Used as a label for the flag.
2193       */
2194      public function __construct($enabled, $default, $shortname, $displayname) {
2195          $this->shortname = $shortname;
2196          $this->displayname = $displayname;
2197          $this->set_options($enabled, $default);
2198      }
2199  
2200      /**
2201       * Update the values of this setting options class
2202       *
2203       * @param bool $enabled Can this option can be toggled.
2204       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2205       * @param bool $default The default checked state for this setting option.
2206       */
2207      public function set_options($enabled, $default) {
2208          $this->enabled = $enabled;
2209          $this->default = $default;
2210      }
2211  
2212      /**
2213       * Should this option appear in the interface and be toggleable?
2214       *
2215       * @return bool Is it enabled?
2216       */
2217      public function is_enabled() {
2218          return $this->enabled;
2219      }
2220  
2221      /**
2222       * Should this option be checked by default?
2223       *
2224       * @return bool Is it on by default?
2225       */
2226      public function get_default() {
2227          return $this->default;
2228      }
2229  
2230      /**
2231       * Return the short name for this flag. e.g. 'adv' or 'locked'
2232       *
2233       * @return string
2234       */
2235      public function get_shortname() {
2236          return $this->shortname;
2237      }
2238  
2239      /**
2240       * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
2241       *
2242       * @return string
2243       */
2244      public function get_displayname() {
2245          return $this->displayname;
2246      }
2247  
2248      /**
2249       * Save the submitted data for this flag - or set it to the default if $data is null.
2250       *
2251       * @param admin_setting $setting - The admin setting for this flag
2252       * @param array $data - The data submitted from the form or null to set the default value for new installs.
2253       * @return bool
2254       */
2255      public function write_setting_flag(admin_setting $setting, $data) {
2256          $result = true;
2257          if ($this->is_enabled()) {
2258              if (!isset($data)) {
2259                  $value = $this->get_default();
2260              } else {
2261                  $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
2262              }
2263              $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
2264          }
2265  
2266          return $result;
2267  
2268      }
2269  
2270      /**
2271       * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
2272       *
2273       * @param admin_setting $setting - The admin setting for this flag
2274       * @return string - The html for the checkbox.
2275       */
2276      public function output_setting_flag(admin_setting $setting) {
2277          global $OUTPUT;
2278  
2279          $value = $setting->get_setting_flag_value($this);
2280  
2281          $context = new stdClass();
2282          $context->id = $setting->get_id() . '_' . $this->get_shortname();
2283          $context->name = $setting->get_full_name() .  '_' . $this->get_shortname();
2284          $context->value = 1;
2285          $context->checked = $value ? true : false;
2286          $context->label = $this->get_displayname();
2287  
2288          return $OUTPUT->render_from_template('core_admin/setting_flag', $context);
2289      }
2290  }
2291  
2292  
2293  /**
2294   * No setting - just heading and text.
2295   *
2296   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2297   */
2298  class admin_setting_heading extends admin_setting {
2299  
2300      /**
2301       * not a setting, just text
2302       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2303       * @param string $heading heading
2304       * @param string $information text in box
2305       */
2306      public function __construct($name, $heading, $information) {
2307          $this->nosave = true;
2308          parent::__construct($name, $heading, $information, '');
2309      }
2310  
2311      /**
2312       * Always returns true
2313       * @return bool Always returns true
2314       */
2315      public function get_setting() {
2316          return true;
2317      }
2318  
2319      /**
2320       * Always returns true
2321       * @return bool Always returns true
2322       */
2323      public function get_defaultsetting() {
2324          return true;
2325      }
2326  
2327      /**
2328       * Never write settings
2329       * @return string Always returns an empty string
2330       */
2331      public function write_setting($data) {
2332      // do not write any setting
2333          return '';
2334      }
2335  
2336      /**
2337       * Returns an HTML string
2338       * @return string Returns an HTML string
2339       */
2340      public function output_html($data, $query='') {
2341          global $OUTPUT;
2342          $context = new stdClass();
2343          $context->title = $this->visiblename;
2344          $context->description = $this->description;
2345          $context->descriptionformatted = highlight($query, markdown_to_html($this->description));
2346          return $OUTPUT->render_from_template('core_admin/setting_heading', $context);
2347      }
2348  }
2349  
2350  /**
2351   * No setting - just name and description in same row.
2352   *
2353   * @copyright 2018 onwards Amaia Anabitarte
2354   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2355   */
2356  class admin_setting_description extends admin_setting {
2357  
2358      /**
2359       * Not a setting, just text
2360       *
2361       * @param string $name
2362       * @param string $visiblename
2363       * @param string $description
2364       */
2365      public function __construct($name, $visiblename, $description) {
2366          $this->nosave = true;
2367          parent::__construct($name, $visiblename, $description, '');
2368      }
2369  
2370      /**
2371       * Always returns true
2372       *
2373       * @return bool Always returns true
2374       */
2375      public function get_setting() {
2376          return true;
2377      }
2378  
2379      /**
2380       * Always returns true
2381       *
2382       * @return bool Always returns true
2383       */
2384      public function get_defaultsetting() {
2385          return true;
2386      }
2387  
2388      /**
2389       * Never write settings
2390       *
2391       * @param mixed $data Gets converted to str for comparison against yes value
2392       * @return string Always returns an empty string
2393       */
2394      public function write_setting($data) {
2395          // Do not write any setting.
2396          return '';
2397      }
2398  
2399      /**
2400       * Returns an HTML string
2401       *
2402       * @param string $data
2403       * @param string $query
2404       * @return string Returns an HTML string
2405       */
2406      public function output_html($data, $query='') {
2407          global $OUTPUT;
2408  
2409          $context = new stdClass();
2410          $context->title = $this->visiblename;
2411          $context->description = $this->description;
2412  
2413          return $OUTPUT->render_from_template('core_admin/setting_description', $context);
2414      }
2415  }
2416  
2417  
2418  
2419  /**
2420   * The most flexible setting, the user enters text.
2421   *
2422   * This type of field should be used for config settings which are using
2423   * English words and are not localised (passwords, database name, list of values, ...).
2424   *
2425   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2426   */
2427  class admin_setting_configtext extends admin_setting {
2428  
2429      /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
2430      public $paramtype;
2431      /** @var int default field size */
2432      public $size;
2433  
2434      /**
2435       * Config text constructor
2436       *
2437       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2438       * @param string $visiblename localised
2439       * @param string $description long localised info
2440       * @param string $defaultsetting
2441       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2442       * @param int $size default field size
2443       */
2444      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2445          $this->paramtype = $paramtype;
2446          if (!is_null($size)) {
2447              $this->size  = $size;
2448          } else {
2449              $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
2450          }
2451          parent::__construct($name, $visiblename, $description, $defaultsetting);
2452      }
2453  
2454      /**
2455       * Get whether this should be displayed in LTR mode.
2456       *
2457       * Try to guess from the PARAM type unless specifically set.
2458       */
2459      public function get_force_ltr() {
2460          $forceltr = parent::get_force_ltr();
2461          if ($forceltr === null) {
2462              return !is_rtl_compatible($this->paramtype);
2463          }
2464          return $forceltr;
2465      }
2466  
2467      /**
2468       * Return the setting
2469       *
2470       * @return mixed returns config if successful else null
2471       */
2472      public function get_setting() {
2473          return $this->config_read($this->name);
2474      }
2475  
2476      public function write_setting($data) {
2477          if ($this->paramtype === PARAM_INT and $data === '') {
2478          // do not complain if '' used instead of 0
2479              $data = 0;
2480          }
2481          // $data is a string
2482          $validated = $this->validate($data);
2483          if ($validated !== true) {
2484              return $validated;
2485          }
2486          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2487      }
2488  
2489      /**
2490       * Validate data before storage
2491       * @param string data
2492       * @return mixed true if ok string if error found
2493       */
2494      public function validate($data) {
2495          // allow paramtype to be a custom regex if it is the form of /pattern/
2496          if (preg_match('#^/.*/$#', $this->paramtype)) {
2497              if (preg_match($this->paramtype, $data)) {
2498                  return true;
2499              } else {
2500                  return get_string('validateerror', 'admin');
2501              }
2502  
2503          } else if ($this->paramtype === PARAM_RAW) {
2504              return true;
2505  
2506          } else {
2507              $cleaned = clean_param($data, $this->paramtype);
2508              if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
2509                  return true;
2510              } else {
2511                  return get_string('validateerror', 'admin');
2512              }
2513          }
2514      }
2515  
2516      /**
2517       * Return an XHTML string for the setting
2518       * @return string Returns an XHTML string
2519       */
2520      public function output_html($data, $query='') {
2521          global $OUTPUT;
2522  
2523          $default = $this->get_defaultsetting();
2524          $context = (object) [
2525              'size' => $this->size,
2526              'id' => $this->get_id(),
2527              'name' => $this->get_full_name(),
2528              'value' => $data,
2529              'forceltr' => $this->get_force_ltr(),
2530              'readonly' => $this->is_readonly(),
2531          ];
2532          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
2533  
2534          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2535      }
2536  }
2537  
2538  /**
2539   * Text input with a maximum length constraint.
2540   *
2541   * @copyright 2015 onwards Ankit Agarwal
2542   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2543   */
2544  class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
2545  
2546      /** @var int maximum number of chars allowed. */
2547      protected $maxlength;
2548  
2549      /**
2550       * Config text constructor
2551       *
2552       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2553       *                     or 'myplugin/mysetting' for ones in config_plugins.
2554       * @param string $visiblename localised
2555       * @param string $description long localised info
2556       * @param string $defaultsetting
2557       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2558       * @param int $size default field size
2559       * @param mixed $maxlength int maxlength allowed, 0 for infinite.
2560       */
2561      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW,
2562                                  $size=null, $maxlength = 0) {
2563          $this->maxlength = $maxlength;
2564          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
2565      }
2566  
2567      /**
2568       * Validate data before storage
2569       *
2570       * @param string $data data
2571       * @return mixed true if ok string if error found
2572       */
2573      public function validate($data) {
2574          $parentvalidation = parent::validate($data);
2575          if ($parentvalidation === true) {
2576              if ($this->maxlength > 0) {
2577                  // Max length check.
2578                  $length = core_text::strlen($data);
2579                  if ($length > $this->maxlength) {
2580                      return get_string('maximumchars', 'moodle',  $this->maxlength);
2581                  }
2582                  return true;
2583              } else {
2584                  return true; // No max length check needed.
2585              }
2586          } else {
2587              return $parentvalidation;
2588          }
2589      }
2590  }
2591  
2592  /**
2593   * General text area without html editor.
2594   *
2595   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2596   */
2597  class admin_setting_configtextarea extends admin_setting_configtext {
2598      private $rows;
2599      private $cols;
2600  
2601      /**
2602       * @param string $name
2603       * @param string $visiblename
2604       * @param string $description
2605       * @param mixed $defaultsetting string or array
2606       * @param mixed $paramtype
2607       * @param string $cols The number of columns to make the editor
2608       * @param string $rows The number of rows to make the editor
2609       */
2610      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2611          $this->rows = $rows;
2612          $this->cols = $cols;
2613          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
2614      }
2615  
2616      /**
2617       * Returns an XHTML string for the editor
2618       *
2619       * @param string $data
2620       * @param string $query
2621       * @return string XHTML string for the editor
2622       */
2623      public function output_html($data, $query='') {
2624          global $OUTPUT;
2625  
2626          $default = $this->get_defaultsetting();
2627          $defaultinfo = $default;
2628          if (!is_null($default) and $default !== '') {
2629              $defaultinfo = "\n".$default;
2630          }
2631  
2632          $context = (object) [
2633              'cols' => $this->cols,
2634              'rows' => $this->rows,
2635              'id' => $this->get_id(),
2636              'name' => $this->get_full_name(),
2637              'value' => $data,
2638              'forceltr' => $this->get_force_ltr(),
2639              'readonly' => $this->is_readonly(),
2640          ];
2641          $element = $OUTPUT->render_from_template('core_admin/setting_configtextarea', $context);
2642  
2643          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
2644      }
2645  }
2646  
2647  /**
2648   * General text area with html editor.
2649   */
2650  class admin_setting_confightmleditor extends admin_setting_configtextarea {
2651  
2652      /**
2653       * @param string $name
2654       * @param string $visiblename
2655       * @param string $description
2656       * @param mixed $defaultsetting string or array
2657       * @param mixed $paramtype
2658       */
2659      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2660          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
2661          $this->set_force_ltr(false);
2662          editors_head_setup();
2663      }
2664  
2665      /**
2666       * Returns an XHTML string for the editor
2667       *
2668       * @param string $data
2669       * @param string $query
2670       * @return string XHTML string for the editor
2671       */
2672      public function output_html($data, $query='') {
2673          $editor = editors_get_preferred_editor(FORMAT_HTML);
2674          $editor->set_text($data);
2675          $editor->use_editor($this->get_id(), array('noclean'=>true));
2676          return parent::output_html($data, $query);
2677      }
2678  
2679      /**
2680       * Checks if data has empty html.
2681       *
2682       * @param string $data
2683       * @return string Empty when no errors.
2684       */
2685      public function write_setting($data) {
2686          if (trim(html_to_text($data)) === '') {
2687              $data = '';
2688          }
2689          return parent::write_setting($data);
2690      }
2691  }
2692  
2693  
2694  /**
2695   * Password field, allows unmasking of password
2696   *
2697   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2698   */
2699  class admin_setting_configpasswordunmask extends admin_setting_configtext {
2700  
2701      /**
2702       * Constructor
2703       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2704       * @param string $visiblename localised
2705       * @param string $description long localised info
2706       * @param string $defaultsetting default password
2707       */
2708      public function __construct($name, $visiblename, $description, $defaultsetting) {
2709          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2710      }
2711  
2712      /**
2713       * Log config changes if necessary.
2714       * @param string $name
2715       * @param string $oldvalue
2716       * @param string $value
2717       */
2718      protected function add_to_config_log($name, $oldvalue, $value) {
2719          if ($value !== '') {
2720              $value = '********';
2721          }
2722          if ($oldvalue !== '' and $oldvalue !== null) {
2723              $oldvalue = '********';
2724          }
2725          parent::add_to_config_log($name, $oldvalue, $value);
2726      }
2727  
2728      /**
2729       * Returns HTML for the field.
2730       *
2731       * @param   string  $data       Value for the field
2732       * @param   string  $query      Passed as final argument for format_admin_setting
2733       * @return  string              Rendered HTML
2734       */
2735      public function output_html($data, $query='') {
2736          global $OUTPUT;
2737  
2738          $context = (object) [
2739              'id' => $this->get_id(),
2740              'name' => $this->get_full_name(),
2741              'size' => $this->size,
2742              'value' => $this->is_readonly() ? null : $data,
2743              'forceltr' => $this->get_force_ltr(),
2744              'readonly' => $this->is_readonly(),
2745          ];
2746          $element = $OUTPUT->render_from_template('core_admin/setting_configpasswordunmask', $context);
2747          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', null, $query);
2748      }
2749  }
2750  
2751  /**
2752   * Password field, allows unmasking of password, with an advanced checkbox that controls an additional $name.'_adv' setting.
2753   *
2754   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2755   * @copyright 2018 Paul Holden (pholden@greenhead.ac.uk)
2756   */
2757  class admin_setting_configpasswordunmask_with_advanced extends admin_setting_configpasswordunmask {
2758  
2759      /**
2760       * Constructor
2761       *
2762       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2763       * @param string $visiblename localised
2764       * @param string $description long localised info
2765       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
2766       */
2767      public function __construct($name, $visiblename, $description, $defaultsetting) {
2768          parent::__construct($name, $visiblename, $description, $defaultsetting['value']);
2769          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
2770      }
2771  }
2772  
2773  /**
2774   * Admin setting class for encrypted values using secure encryption.
2775   *
2776   * @copyright 2019 The Open University
2777   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2778   */
2779  class admin_setting_encryptedpassword extends admin_setting {
2780  
2781      /**
2782       * Constructor. Same as parent except that the default value is always an empty string.
2783       *
2784       * @param string $name Internal name used in config table
2785       * @param string $visiblename Name shown on form
2786       * @param string $description Description that appears below field
2787       */
2788      public function __construct(string $name, string $visiblename, string $description) {
2789          parent::__construct($name, $visiblename, $description, '');
2790      }
2791  
2792      public function get_setting() {
2793          return $this->config_read($this->name);
2794      }
2795  
2796      public function write_setting($data) {
2797          $data = trim($data);
2798          if ($data === '') {
2799              // Value can really be set to nothing.
2800              $savedata = '';
2801          } else {
2802              // Encrypt value before saving it.
2803              $savedata = \core\encryption::encrypt($data);
2804          }
2805          return ($this->config_write($this->name, $savedata) ? '' : get_string('errorsetting', 'admin'));
2806      }
2807  
2808      public function output_html($data, $query='') {
2809          global $OUTPUT;
2810  
2811          $default = $this->get_defaultsetting();
2812          $context = (object) [
2813              'id' => $this->get_id(),
2814              'name' => $this->get_full_name(),
2815              'set' => $data !== '',
2816              'novalue' => $this->get_setting() === null
2817          ];
2818          $element = $OUTPUT->render_from_template('core_admin/setting_encryptedpassword', $context);
2819  
2820          return format_admin_setting($this, $this->visiblename, $element, $this->description,
2821                  true, '', $default, $query);
2822      }
2823  }
2824  
2825  /**
2826   * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
2827   * Note: Only advanced makes sense right now - locked does not.
2828   *
2829   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2830   */
2831  class admin_setting_configempty extends admin_setting_configtext {
2832  
2833      /**
2834       * @param string $name
2835       * @param string $visiblename
2836       * @param string $description
2837       */
2838      public function __construct($name, $visiblename, $description) {
2839          parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
2840      }
2841  
2842      /**
2843       * Returns an XHTML string for the hidden field
2844       *
2845       * @param string $data
2846       * @param string $query
2847       * @return string XHTML string for the editor
2848       */
2849      public function output_html($data, $query='') {
2850          global $OUTPUT;
2851  
2852          $context = (object) [
2853              'id' => $this->get_id(),
2854              'name' => $this->get_full_name()
2855          ];
2856          $element = $OUTPUT->render_from_template('core_admin/setting_configempty', $context);
2857  
2858          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', get_string('none'), $query);
2859      }
2860  }
2861  
2862  
2863  /**
2864   * Path to directory
2865   *
2866   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2867   */
2868  class admin_setting_configfile extends admin_setting_configtext {
2869      /**
2870       * Constructor
2871       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2872       * @param string $visiblename localised
2873       * @param string $description long localised info
2874       * @param string $defaultdirectory default directory location
2875       */
2876      public function __construct($name, $visiblename, $description, $defaultdirectory) {
2877          parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2878      }
2879  
2880      /**
2881       * Returns XHTML for the field
2882       *
2883       * Returns XHTML for the field and also checks whether the file
2884       * specified in $data exists using file_exists()
2885       *
2886       * @param string $data File name and path to use in value attr
2887       * @param string $query
2888       * @return string XHTML field
2889       */
2890      public function output_html($data, $query='') {
2891          global $CFG, $OUTPUT;
2892  
2893          $default = $this->get_defaultsetting();
2894          $context = (object) [
2895              'id' => $this->get_id(),
2896              'name' => $this->get_full_name(),
2897              'size' => $this->size,
2898              'value' => $data,
2899              'showvalidity' => !empty($data),
2900              'valid' => $data && file_exists($data),
2901              'readonly' => !empty($CFG->preventexecpath) || $this->is_readonly(),
2902              'forceltr' => $this->get_force_ltr(),
2903          ];
2904  
2905          if ($context->readonly) {
2906              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2907          }
2908  
2909          $element = $OUTPUT->render_from_template('core_admin/setting_configfile', $context);
2910  
2911          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2912      }
2913  
2914      /**
2915       * Checks if execpatch has been disabled in config.php
2916       */
2917      public function write_setting($data) {
2918          global $CFG;
2919          if (!empty($CFG->preventexecpath)) {
2920              if ($this->get_setting() === null) {
2921                  // Use default during installation.
2922                  $data = $this->get_defaultsetting();
2923                  if ($data === null) {
2924                      $data = '';
2925                  }
2926              } else {
2927                  return '';
2928              }
2929          }
2930          return parent::write_setting($data);
2931      }
2932  
2933  }
2934  
2935  
2936  /**
2937   * Path to executable file
2938   *
2939   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2940   */
2941  class admin_setting_configexecutable extends admin_setting_configfile {
2942  
2943      /**
2944       * Returns an XHTML field
2945       *
2946       * @param string $data This is the value for the field
2947       * @param string $query
2948       * @return string XHTML field
2949       */
2950      public function output_html($data, $query='') {
2951          global $CFG, $OUTPUT;
2952          $default = $this->get_defaultsetting();
2953          require_once("$CFG->libdir/filelib.php");
2954  
2955          $context = (object) [
2956              'id' => $this->get_id(),
2957              'name' => $this->get_full_name(),
2958              'size' => $this->size,
2959              'value' => $data,
2960              'showvalidity' => !empty($data),
2961              'valid' => $data && file_exists($data) && !is_dir($data) && file_is_executable($data),
2962              'readonly' => !empty($CFG->preventexecpath),
2963              'forceltr' => $this->get_force_ltr()
2964          ];
2965  
2966          if (!empty($CFG->preventexecpath)) {
2967              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2968          }
2969  
2970          $element = $OUTPUT->render_from_template('core_admin/setting_configexecutable', $context);
2971  
2972          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2973      }
2974  }
2975  
2976  
2977  /**
2978   * Path to directory
2979   *
2980   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2981   */
2982  class admin_setting_configdirectory extends admin_setting_configfile {
2983  
2984      /**
2985       * Returns an XHTML field
2986       *
2987       * @param string $data This is the value for the field
2988       * @param string $query
2989       * @return string XHTML
2990       */
2991      public function output_html($data, $query='') {
2992          global $CFG, $OUTPUT;
2993          $default = $this->get_defaultsetting();
2994  
2995          $context = (object) [
2996              'id' => $this->get_id(),
2997              'name' => $this->get_full_name(),
2998              'size' => $this->size,
2999              'value' => $data,
3000              'showvalidity' => !empty($data),
3001              'valid' => $data && file_exists($data) && is_dir($data),
3002              'readonly' => !empty($CFG->preventexecpath),
3003              'forceltr' => $this->get_force_ltr()
3004          ];
3005  
3006          if (!empty($CFG->preventexecpath)) {
3007              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
3008          }
3009  
3010          $element = $OUTPUT->render_from_template('core_admin/setting_configdirectory', $context);
3011  
3012          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
3013      }
3014  }
3015  
3016  
3017  /**
3018   * Checkbox
3019   *
3020   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3021   */
3022  class admin_setting_configcheckbox extends admin_setting {
3023      /** @var string Value used when checked */
3024      public $yes;
3025      /** @var string Value used when not checked */
3026      public $no;
3027  
3028      /**
3029       * Constructor
3030       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3031       * @param string $visiblename localised
3032       * @param string $description long localised info
3033       * @param string $defaultsetting
3034       * @param string $yes value used when checked
3035       * @param string $no value used when not checked
3036       */
3037      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
3038          parent::__construct($name, $visiblename, $description, $defaultsetting);
3039          $this->yes = (string)$yes;
3040          $this->no  = (string)$no;
3041      }
3042  
3043      /**
3044       * Retrieves the current setting using the objects name
3045       *
3046       * @return string
3047       */
3048      public function get_setting() {
3049          return $this->config_read($this->name);
3050      }
3051  
3052      /**
3053       * Sets the value for the setting
3054       *
3055       * Sets the value for the setting to either the yes or no values
3056       * of the object by comparing $data to yes
3057       *
3058       * @param mixed $data Gets converted to str for comparison against yes value
3059       * @return string empty string or error
3060       */
3061      public function write_setting($data) {
3062          if ((string)$data === $this->yes) { // convert to strings before comparison
3063              $data = $this->yes;
3064          } else {
3065              $data = $this->no;
3066          }
3067          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3068      }
3069  
3070      /**
3071       * Returns an XHTML checkbox field
3072       *
3073       * @param string $data If $data matches yes then checkbox is checked
3074       * @param string $query
3075       * @return string XHTML field
3076       */
3077      public function output_html($data, $query='') {
3078          global $OUTPUT;
3079  
3080          $context = (object) [
3081              'id' => $this->get_id(),
3082              'name' => $this->get_full_name(),
3083              'no' => $this->no,
3084              'value' => $this->yes,
3085              'checked' => (string) $data === $this->yes,
3086              'readonly' => $this->is_readonly(),
3087          ];
3088  
3089          $default = $this->get_defaultsetting();
3090          if (!is_null($default)) {
3091              if ((string)$default === $this->yes) {
3092                  $defaultinfo = get_string('checkboxyes', 'admin');
3093              } else {
3094                  $defaultinfo = get_string('checkboxno', 'admin');
3095              }
3096          } else {
3097              $defaultinfo = NULL;
3098          }
3099  
3100          $element = $OUTPUT->render_from_template('core_admin/setting_configcheckbox', $context);
3101  
3102          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3103      }
3104  }
3105  
3106  
3107  /**
3108   * Multiple checkboxes, each represents different value, stored in csv format
3109   *
3110   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3111   */
3112  class admin_setting_configmulticheckbox extends admin_setting {
3113      /** @var array Array of choices value=>label */
3114      public $choices;
3115      /** @var callable|null Loader function for choices */
3116      protected $choiceloader = null;
3117  
3118      /**
3119       * Constructor: uses parent::__construct
3120       *
3121       * The $choices parameter may be either an array of $value => $label format,
3122       * e.g. [1 => get_string('yes')], or a callback function which takes no parameters and
3123       * returns an array in that format.
3124       *
3125       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3126       * @param string $visiblename localised
3127       * @param string $description long localised info
3128       * @param array $defaultsetting array of selected
3129       * @param array|callable $choices array of $value => $label for each checkbox, or a callback
3130       */
3131      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3132          if (is_array($choices)) {
3133              $this->choices = $choices;
3134          }
3135          if (is_callable($choices)) {
3136              $this->choiceloader = $choices;
3137          }
3138          parent::__construct($name, $visiblename, $description, $defaultsetting);
3139      }
3140  
3141      /**
3142       * This function may be used in ancestors for lazy loading of choices
3143       *
3144       * Override this method if loading of choices is expensive, such
3145       * as when it requires multiple db requests.
3146       *
3147       * @return bool true if loaded, false if error
3148       */
3149      public function load_choices() {
3150          if ($this->choiceloader) {
3151              if (!is_array($this->choices)) {
3152                  $this->choices = call_user_func($this->choiceloader);
3153              }
3154          }
3155          return true;
3156      }
3157  
3158      /**
3159       * Is setting related to query text - used when searching
3160       *
3161       * @param string $query
3162       * @return bool true on related, false on not or failure
3163       */
3164      public function is_related($query) {
3165          if (!$this->load_choices() or empty($this->choices)) {
3166              return false;
3167          }
3168          if (parent::is_related($query)) {
3169              return true;
3170          }
3171  
3172          foreach ($this->choices as $desc) {
3173              if (strpos(core_text::strtolower($desc), $query) !== false) {
3174                  return true;
3175              }
3176          }
3177          return false;
3178      }
3179  
3180      /**
3181       * Returns the current setting if it is set
3182       *
3183       * @return mixed null if null, else an array
3184       */
3185      public function get_setting() {
3186          $result = $this->config_read($this->name);
3187  
3188          if (is_null($result)) {
3189              return NULL;
3190          }
3191          if ($result === '') {
3192              return array();
3193          }
3194          $enabled = explode(',', $result);
3195          $setting = array();
3196          foreach ($enabled as $option) {
3197              $setting[$option] = 1;
3198          }
3199          return $setting;
3200      }
3201  
3202      /**
3203       * Saves the setting(s) provided in $data
3204       *
3205       * @param array $data An array of data, if not array returns empty str
3206       * @return mixed empty string on useless data or bool true=success, false=failed
3207       */
3208      public function write_setting($data) {
3209          if (!is_array($data)) {
3210              return ''; // ignore it
3211          }
3212          if (!$this->load_choices() or empty($this->choices)) {
3213              return '';
3214          }
3215          unset($data['xxxxx']);
3216          $result = array();
3217          foreach ($data as $key => $value) {
3218              if ($value and array_key_exists($key, $this->choices)) {
3219                  $result[] = $key;
3220              }
3221          }
3222          return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
3223      }
3224  
3225      /**
3226       * Returns XHTML field(s) as required by choices
3227       *
3228       * Relies on data being an array should data ever be another valid vartype with
3229       * acceptable value this may cause a warning/error
3230       * if (!is_array($data)) would fix the problem
3231       *
3232       * @todo Add vartype handling to ensure $data is an array
3233       *
3234       * @param array $data An array of checked values
3235       * @param string $query
3236       * @return string XHTML field
3237       */
3238      public function output_html($data, $query='') {
3239          global $OUTPUT;
3240  
3241          if (!$this->load_choices() or empty($this->choices)) {
3242              return '';
3243          }
3244  
3245          $default = $this->get_defaultsetting();
3246          if (is_null($default)) {
3247              $default = array();
3248          }
3249          if (is_null($data)) {
3250              $data = array();
3251          }
3252  
3253          $context = (object) [
3254              'id' => $this->get_id(),
3255              'name' => $this->get_full_name(),
3256          ];
3257  
3258          $options = array();
3259          $defaults = array();
3260          foreach ($this->choices as $key => $description) {
3261              if (!empty($default[$key])) {
3262                  $defaults[] = $description;
3263              }
3264  
3265              $options[] = [
3266                  'key' => $key,
3267                  'checked' => !empty($data[$key]),
3268                  'label' => highlightfast($query, $description)
3269              ];
3270          }
3271  
3272          if (is_null($default)) {
3273              $defaultinfo = null;
3274          } else if (!empty($defaults)) {
3275              $defaultinfo = implode(', ', $defaults);
3276          } else {
3277              $defaultinfo = get_string('none');
3278          }
3279  
3280          $context->options = $options;
3281          $context->hasoptions = !empty($options);
3282  
3283          $element = $OUTPUT->render_from_template('core_admin/setting_configmulticheckbox', $context);
3284  
3285          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', $defaultinfo, $query);
3286  
3287      }
3288  }
3289  
3290  
3291  /**
3292   * Multiple checkboxes 2, value stored as string 00101011
3293   *
3294   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3295   */
3296  class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
3297  
3298      /**
3299       * Returns the setting if set
3300       *
3301       * @return mixed null if not set, else an array of set settings
3302       */
3303      public function get_setting() {
3304          $result = $this->config_read($this->name);
3305          if (is_null($result)) {
3306              return NULL;
3307          }
3308          if (!$this->load_choices()) {
3309              return NULL;
3310          }
3311          $result = str_pad($result, count($this->choices), '0');
3312          $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
3313          $setting = array();
3314          foreach ($this->choices as $key=>$unused) {
3315              $value = array_shift($result);
3316              if ($value) {
3317                  $setting[$key] = 1;
3318              }
3319          }
3320          return $setting;
3321      }
3322  
3323      /**
3324       * Save setting(s) provided in $data param
3325       *
3326       * @param array $data An array of settings to save
3327       * @return mixed empty string for bad data or bool true=>success, false=>error
3328       */
3329      public function write_setting($data) {
3330          if (!is_array($data)) {
3331              return ''; // ignore it
3332          }
3333          if (!$this->load_choices() or empty($this->choices)) {
3334              return '';
3335          }
3336          $result = '';
3337          foreach ($this->choices as $key=>$unused) {
3338              if (!empty($data[$key])) {
3339                  $result .= '1';
3340              } else {
3341                  $result .= '0';
3342              }
3343          }
3344          return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
3345      }
3346  }
3347  
3348  
3349  /**
3350   * Select one value from list
3351   *
3352   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3353   */
3354  class admin_setting_configselect extends admin_setting {
3355      /** @var array Array of choices value=>label */
3356      public $choices;
3357      /** @var array Array of choices grouped using optgroups */
3358      public $optgroups;
3359      /** @var callable|null Loader function for choices */
3360      protected $choiceloader = null;
3361      /** @var callable|null Validation function */
3362      protected $validatefunction = null;
3363  
3364      /**
3365       * Constructor.
3366       *
3367       * If you want to lazy-load the choices, pass a callback function that returns a choice
3368       * array for the $choices parameter.
3369       *
3370       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3371       * @param string $visiblename localised
3372       * @param string $description long localised info
3373       * @param string|int $defaultsetting
3374       * @param array|callable|null $choices array of $value=>$label for each selection, or callback
3375       */
3376      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3377          // Look for optgroup and single options.
3378          if (is_array($choices)) {
3379              $this->choices = [];
3380              foreach ($choices as $key => $val) {
3381                  if (is_array($val)) {
3382                      $this->optgroups[$key] = $val;
3383                      $this->choices = array_merge($this->choices, $val);
3384                  } else {
3385                      $this->choices[$key] = $val;
3386                  }
3387              }
3388          }
3389          if (is_callable($choices)) {
3390              $this->choiceloader = $choices;
3391          }
3392  
3393          parent::__construct($name, $visiblename, $description, $defaultsetting);
3394      }
3395  
3396      /**
3397       * Sets a validate function.
3398       *
3399       * The callback will be passed one parameter, the new setting value, and should return either
3400       * an empty string '' if the value is OK, or an error message if not.
3401       *
3402       * @param callable|null $validatefunction Validate function or null to clear
3403       * @since Moodle 3.10
3404       */
3405      public function set_validate_function(?callable $validatefunction = null) {
3406          $this->validatefunction = $validatefunction;
3407      }
3408  
3409      /**
3410       * This function may be used in ancestors for lazy loading of choices
3411       *
3412       * Override this method if loading of choices is expensive, such
3413       * as when it requires multiple db requests.
3414       *
3415       * @return bool true if loaded, false if error
3416       */
3417      public function load_choices() {
3418          if ($this->choiceloader) {
3419              if (!is_array($this->choices)) {
3420                  $this->choices = call_user_func($this->choiceloader);
3421              }
3422              return true;
3423          }
3424          return true;
3425      }
3426  
3427      /**
3428       * Check if this is $query is related to a choice
3429       *
3430       * @param string $query
3431       * @return bool true if related, false if not
3432       */
3433      public function is_related($query) {
3434          if (parent::is_related($query)) {
3435              return true;
3436          }
3437          if (!$this->load_choices()) {
3438              return false;
3439          }
3440          foreach ($this->choices as $key=>$value) {
3441              if (strpos(core_text::strtolower($key), $query) !== false) {
3442                  return true;
3443              }
3444              if (strpos(core_text::strtolower($value), $query) !== false) {
3445                  return true;
3446              }
3447          }
3448          return false;
3449      }
3450  
3451      /**
3452       * Return the setting
3453       *
3454       * @return mixed returns config if successful else null
3455       */
3456      public function get_setting() {
3457          return $this->config_read($this->name);
3458      }
3459  
3460      /**
3461       * Save a setting
3462       *
3463       * @param string $data
3464       * @return string empty of error string
3465       */
3466      public function write_setting($data) {
3467          if (!$this->load_choices() or empty($this->choices)) {
3468              return '';
3469          }
3470          if (!array_key_exists($data, $this->choices)) {
3471              return ''; // ignore it
3472          }
3473  
3474          // Validate the new setting.
3475          $error = $this->validate_setting($data);
3476          if ($error) {
3477              return $error;
3478          }
3479  
3480          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3481      }
3482  
3483      /**
3484       * Validate the setting. This uses the callback function if provided; subclasses could override
3485       * to carry out validation directly in the class.
3486       *
3487       * @param string $data New value being set
3488       * @return string Empty string if valid, or error message text
3489       * @since Moodle 3.10
3490       */
3491      protected function validate_setting(string $data): string {
3492          // If validation function is specified, call it now.
3493          if ($this->validatefunction) {
3494              return call_user_func($this->validatefunction, $data);
3495          } else {
3496              return '';
3497          }
3498      }
3499  
3500      /**
3501       * Returns XHTML select field
3502       *
3503       * Ensure the options are loaded, and generate the XHTML for the select
3504       * element and any warning message. Separating this out from output_html
3505       * makes it easier to subclass this class.
3506       *
3507       * @param string $data the option to show as selected.
3508       * @param string $current the currently selected option in the database, null if none.
3509       * @param string $default the default selected option.
3510       * @return array the HTML for the select element, and a warning message.
3511       * @deprecated since Moodle 3.2
3512       */
3513      public function output_select_html($data, $current, $default, $extraname = '') {
3514          debugging('The method admin_setting_configselect::output_select_html is depreacted, do not use any more.', DEBUG_DEVELOPER);
3515      }
3516  
3517      /**
3518       * Returns XHTML select field and wrapping div(s)
3519       *
3520       * @see output_select_html()
3521       *
3522       * @param string $data the option to show as selected
3523       * @param string $query
3524       * @return string XHTML field and wrapping div
3525       */
3526      public function output_html($data, $query='') {
3527          global $OUTPUT;
3528  
3529          $default = $this->get_defaultsetting();
3530          $current = $this->get_setting();
3531  
3532          if (!$this->load_choices() || empty($this->choices)) {
3533              return '';
3534          }
3535  
3536          $context = (object) [
3537              'id' => $this->get_id(),
3538              'name' => $this->get_full_name(),
3539          ];
3540  
3541          if (!is_null($default) && array_key_exists($default, $this->choices)) {
3542              $defaultinfo = $this->choices[$default];
3543          } else {
3544              $defaultinfo = NULL;
3545          }
3546  
3547          // Warnings.
3548          $warning = '';
3549          if ($current === null) {
3550              // First run.
3551          } else if (empty($current) && (array_key_exists('', $this->choices) || array_key_exists(0, $this->choices))) {
3552              // No warning.
3553          } else if (!array_key_exists($current, $this->choices)) {
3554              $warning = get_string('warningcurrentsetting', 'admin', $current);
3555              if (!is_null($default) && $data == $current) {
3556                  $data = $default; // Use default instead of first value when showing the form.
3557              }
3558          }
3559  
3560          $options = [];
3561          $template = 'core_admin/setting_configselect';
3562  
3563          if (!empty($this->optgroups)) {
3564              $optgroups = [];
3565              foreach ($this->optgroups as $label => $choices) {
3566                  $optgroup = array('label' => $label, 'options' => []);
3567                  foreach ($choices as $value => $name) {
3568                      $optgroup['options'][] = [
3569                          'value' => $value,
3570                          'name' => $name,
3571                          'selected' => (string) $value == $data
3572                      ];
3573                      unset($this->choices[$value]);
3574                  }
3575                  $optgroups[] = $optgroup;
3576              }
3577              $context->options = $options;
3578              $context->optgroups = $optgroups;
3579              $template = 'core_admin/setting_configselect_optgroup';
3580          }
3581  
3582          foreach ($this->choices as $value => $name) {
3583              $options[] = [
3584                  'value' => $value,
3585                  'name' => $name,
3586                  'selected' => (string) $value == $data
3587              ];
3588          }
3589          $context->options = $options;
3590          $context->readonly = $this->is_readonly();
3591  
3592          $element = $OUTPUT->render_from_template($template, $context);
3593  
3594          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, $warning, $defaultinfo, $query);
3595      }
3596  }
3597  
3598  /**
3599   * Select multiple items from list
3600   *
3601   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3602   */
3603  class admin_setting_configmultiselect extends admin_setting_configselect {
3604      /**
3605       * Constructor
3606       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3607       * @param string $visiblename localised
3608       * @param string $description long localised info
3609       * @param array $defaultsetting array of selected items
3610       * @param array $choices array of $value=>$label for each list item
3611       */
3612      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3613          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
3614      }
3615  
3616      /**
3617       * Returns the select setting(s)
3618       *
3619       * @return mixed null or array. Null if no settings else array of setting(s)
3620       */
3621      public function get_setting() {
3622          $result = $this->config_read($this->name);
3623          if (is_null($result)) {
3624              return NULL;
3625          }
3626          if ($result === '') {
3627              return array();
3628          }
3629          return explode(',', $result);
3630      }
3631  
3632      /**
3633       * Saves setting(s) provided through $data
3634       *
3635       * Potential bug in the works should anyone call with this function
3636       * using a vartype that is not an array
3637       *
3638       * @param array $data
3639       */
3640      public function write_setting($data) {
3641          if (!is_array($data)) {
3642              return ''; //ignore it
3643          }
3644          if (!$this->load_choices() or empty($this->choices)) {
3645              return '';
3646          }
3647  
3648          unset($data['xxxxx']);
3649  
3650          $save = array();
3651          foreach ($data as $value) {
3652              if (!array_key_exists($value, $this->choices)) {
3653                  continue; // ignore it
3654              }
3655              $save[] = $value;
3656          }
3657  
3658          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3659      }
3660  
3661      /**
3662       * Is setting related to query text - used when searching
3663       *
3664       * @param string $query
3665       * @return bool true if related, false if not
3666       */
3667      public function is_related($query) {
3668          if (!$this->load_choices() or empty($this->choices)) {
3669              return false;
3670          }
3671          if (parent::is_related($query)) {
3672              return true;
3673          }
3674  
3675          foreach ($this->choices as $desc) {
3676              if (strpos(core_text::strtolower($desc), $query) !== false) {
3677                  return true;
3678              }
3679          }
3680          return false;
3681      }
3682  
3683      /**
3684       * Returns XHTML multi-select field
3685       *
3686       * @todo Add vartype handling to ensure $data is an array
3687       * @param array $data Array of values to select by default
3688       * @param string $query
3689       * @return string XHTML multi-select field
3690       */
3691      public function output_html($data, $query='') {
3692          global $OUTPUT;
3693  
3694          if (!$this->load_choices() or empty($this->choices)) {
3695              return '';
3696          }
3697  
3698          $default = $this->get_defaultsetting();
3699          if (is_null($default)) {
3700              $default = array();
3701          }
3702          if (is_null($data)) {
3703              $data = array();
3704          }
3705  
3706          $context = (object) [
3707              'id' => $this->get_id(),
3708              'name' => $this->get_full_name(),
3709              'size' => min(10, count($this->choices))
3710          ];
3711  
3712          $defaults = [];
3713          $options = [];
3714          $template = 'core_admin/setting_configmultiselect';
3715  
3716          if (!empty($this->optgroups)) {
3717              $optgroups = [];
3718              foreach ($this->optgroups as $label => $choices) {
3719                  $optgroup = array('label' => $label, 'options' => []);
3720                  foreach ($choices as $value => $name) {
3721                      if (in_array($value, $default)) {
3722                          $defaults[] = $name;
3723                      }
3724                      $optgroup['options'][] = [
3725                          'value' => $value,
3726                          'name' => $name,
3727                          'selected' => in_array($value, $data)
3728                      ];
3729                      unset($this->choices[$value]);
3730                  }
3731                  $optgroups[] = $optgroup;
3732              }
3733              $context->optgroups = $optgroups;
3734              $template = 'core_admin/setting_configmultiselect_optgroup';
3735          }
3736  
3737          foreach ($this->choices as $value => $name) {
3738              if (in_array($value, $default)) {
3739                  $defaults[] = $name;
3740              }
3741              $options[] = [
3742                  'value' => $value,
3743                  'name' => $name,
3744                  'selected' => in_array($value, $data)
3745              ];
3746          }
3747          $context->options = $options;
3748          $context->readonly = $this->is_readonly();
3749  
3750          if (is_null($default)) {
3751              $defaultinfo = NULL;
3752          } if (!empty($defaults)) {
3753              $defaultinfo = implode(', ', $defaults);
3754          } else {
3755              $defaultinfo = get_string('none');
3756          }
3757  
3758          $element = $OUTPUT->render_from_template($template, $context);
3759  
3760          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3761      }
3762  }
3763  
3764  /**
3765   * Time selector
3766   *
3767   * This is a liiitle bit messy. we're using two selects, but we're returning
3768   * them as an array named after $name (so we only use $name2 internally for the setting)
3769   *
3770   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3771   */
3772  class admin_setting_configtime extends admin_setting {
3773      /** @var string Used for setting second select (minutes) */
3774      public $name2;
3775  
3776      /**
3777       * Constructor
3778       * @param string $hoursname setting for hours
3779       * @param string $minutesname setting for hours
3780       * @param string $visiblename localised
3781       * @param string $description long localised info
3782       * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3783       */
3784      public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
3785          $this->name2 = $minutesname;
3786          parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
3787      }
3788  
3789      /**
3790       * Get the selected time
3791       *
3792       * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
3793       */
3794      public function get_setting() {
3795          $result1 = $this->config_read($this->name);
3796          $result2 = $this->config_read($this->name2);
3797          if (is_null($result1) or is_null($result2)) {
3798              return NULL;
3799          }
3800  
3801          return array('h' => $result1, 'm' => $result2);
3802      }
3803  
3804      /**
3805       * Store the time (hours and minutes)
3806       *
3807       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3808       * @return bool true if success, false if not
3809       */
3810      public function write_setting($data) {
3811          if (!is_array($data)) {
3812              return '';
3813          }
3814  
3815          $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3816          return ($result ? '' : get_string('errorsetting', 'admin'));
3817      }
3818  
3819      /**
3820       * Returns XHTML time select fields
3821       *
3822       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3823       * @param string $query
3824       * @return string XHTML time select fields and wrapping div(s)
3825       */
3826      public function output_html($data, $query='') {
3827          global $OUTPUT;
3828  
3829          $default = $this->get_defaultsetting();
3830          if (is_array($default)) {
3831              $defaultinfo = $default['h'].':'.$default['m'];
3832          } else {
3833              $defaultinfo = NULL;
3834          }
3835  
3836          $context = (object) [
3837              'id' => $this->get_id(),
3838              'name' => $this->get_full_name(),
3839              'readonly' => $this->is_readonly(),
3840              'hours' => array_map(function($i) use ($data) {
3841                  return [
3842                      'value' => $i,
3843                      'name' => $i,
3844                      'selected' => $i == $data['h']
3845                  ];
3846              }, range(0, 23)),
3847              'minutes' => array_map(function($i) use ($data) {
3848                  return [
3849                      'value' => $i,
3850                      'name' => $i,
3851                      'selected' => $i == $data['m']
3852                  ];
3853              }, range(0, 59, 5))
3854          ];
3855  
3856          $element = $OUTPUT->render_from_template('core_admin/setting_configtime', $context);
3857  
3858          return format_admin_setting($this, $this->visiblename, $element, $this->description,
3859              $this->get_id() . 'h', '', $defaultinfo, $query);
3860      }
3861  
3862  }
3863  
3864  
3865  /**
3866   * Seconds duration setting.
3867   *
3868   * @copyright 2012 Petr Skoda (http://skodak.org)
3869   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3870   */
3871  class admin_setting_configduration extends admin_setting {
3872  
3873      /** @var int default duration unit */
3874      protected $defaultunit;
3875      /** @var callable|null Validation function */
3876      protected $validatefunction = null;
3877  
3878      /**
3879       * Constructor
3880       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3881       *                     or 'myplugin/mysetting' for ones in config_plugins.
3882       * @param string $visiblename localised name
3883       * @param string $description localised long description
3884       * @param mixed $defaultsetting string or array depending on implementation
3885       * @param int $defaultunit - day, week, etc. (in seconds)
3886       */
3887      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3888          if (is_number($defaultsetting)) {
3889              $defaultsetting = self::parse_seconds($defaultsetting);
3890          }
3891          $units = self::get_units();
3892          if (isset($units[$defaultunit])) {
3893              $this->defaultunit = $defaultunit;
3894          } else {
3895              $this->defaultunit = 86400;
3896          }
3897          parent::__construct($name, $visiblename, $description, $defaultsetting);
3898      }
3899  
3900      /**
3901       * Sets a validate function.
3902       *
3903       * The callback will be passed one parameter, the new setting value, and should return either
3904       * an empty string '' if the value is OK, or an error message if not.
3905       *
3906       * @param callable|null $validatefunction Validate function or null to clear
3907       * @since Moodle 3.10
3908       */
3909      public function set_validate_function(?callable $validatefunction = null) {
3910          $this->validatefunction = $validatefunction;
3911      }
3912  
3913      /**
3914       * Validate the setting. This uses the callback function if provided; subclasses could override
3915       * to carry out validation directly in the class.
3916       *
3917       * @param int $data New value being set
3918       * @return string Empty string if valid, or error message text
3919       * @since Moodle 3.10
3920       */
3921      protected function validate_setting(int $data): string {
3922          // If validation function is specified, call it now.
3923          if ($this->validatefunction) {
3924              return call_user_func($this->validatefunction, $data);
3925          } else {
3926              if ($data < 0) {
3927                  return get_string('errorsetting', 'admin');
3928              }
3929              return '';
3930          }
3931      }
3932  
3933      /**
3934       * Returns selectable units.
3935       * @static
3936       * @return array
3937       */
3938      protected static function get_units() {
3939          return array(
3940              604800 => get_string('weeks'),
3941              86400 => get_string('days'),
3942              3600 => get_string('hours'),
3943              60 => get_string('minutes'),
3944              1 => get_string('seconds'),
3945          );
3946      }
3947  
3948      /**
3949       * Converts seconds to some more user friendly string.
3950       * @static
3951       * @param int $seconds
3952       * @return string
3953       */
3954      protected static function get_duration_text($seconds) {
3955          if (empty($seconds)) {
3956              return get_string('none');
3957          }
3958          $data = self::parse_seconds($seconds);
3959          switch ($data['u']) {
3960              case (60*60*24*7):
3961                  return get_string('numweeks', '', $data['v']);
3962              case (60*60*24):
3963                  return get_string('numdays', '', $data['v']);
3964              case (60*60):
3965                  return get_string('numhours', '', $data['v']);
3966              case (60):
3967                  return get_string('numminutes', '', $data['v']);
3968              default:
3969                  return get_string('numseconds', '', $data['v']*$data['u']);
3970          }
3971      }
3972  
3973      /**
3974       * Finds suitable units for given duration.
3975       * @static
3976       * @param int $seconds
3977       * @return array
3978       */
3979      protected static function parse_seconds($seconds) {
3980          foreach (self::get_units() as $unit => $unused) {
3981              if ($seconds % $unit === 0) {
3982                  return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
3983              }
3984          }
3985          return array('v'=>(int)$seconds, 'u'=>1);
3986      }
3987  
3988      /**
3989       * Get the selected duration as array.
3990       *
3991       * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
3992       */
3993      public function get_setting() {
3994          $seconds = $this->config_read($this->name);
3995          if (is_null($seconds)) {
3996              return null;
3997          }
3998  
3999          return self::parse_seconds($seconds);
4000      }
4001  
4002      /**
4003       * Store the duration as seconds.
4004       *
4005       * @param array $data Must be form 'h'=>xx, 'm'=>xx
4006       * @return bool true if success, false if not
4007       */
4008      public function write_setting($data) {
4009          if (!is_array($data)) {
4010              return '';
4011          }
4012  
4013          $unit = (int)$data['u'];
4014          $value = (int)$data['v'];
4015          $seconds = $value * $unit;
4016  
4017          // Validate the new setting.
4018          $error = $this->validate_setting($seconds);
4019          if ($error) {
4020              return $error;
4021          }
4022  
4023          $result = $this->config_write($this->name, $seconds);
4024          return ($result ? '' : get_string('errorsetting', 'admin'));
4025      }
4026  
4027      /**
4028       * Returns duration text+select fields.
4029       *
4030       * @param array $data Must be form 'v'=>xx, 'u'=>xx
4031       * @param string $query
4032       * @return string duration text+select fields and wrapping div(s)
4033       */
4034      public function output_html($data, $query='') {
4035          global $OUTPUT;
4036  
4037          $default = $this->get_defaultsetting();
4038          if (is_number($default)) {
4039              $defaultinfo = self::get_duration_text($default);
4040          } else if (is_array($default)) {
4041              $defaultinfo = self::get_duration_text($default['v']*$default['u']);
4042          } else {
4043              $defaultinfo = null;
4044          }
4045  
4046          $inputid = $this->get_id() . 'v';
4047          $units = self::get_units();
4048          $defaultunit = $this->defaultunit;
4049  
4050          $context = (object) [
4051              'id' => $this->get_id(),
4052              'name' => $this->get_full_name(),
4053              'value' => $data['v'] ?? '',
4054              'readonly' => $this->is_readonly(),
4055              'options' => array_map(function($unit) use ($units, $data, $defaultunit) {
4056                  return [
4057                      'value' => $unit,
4058                      'name' => $units[$unit],
4059                      'selected' => isset($data) && (($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u'])
4060                  ];
4061              }, array_keys($units))
4062          ];
4063  
4064          $element = $OUTPUT->render_from_template('core_admin/setting_configduration', $context);
4065  
4066          return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
4067      }
4068  }
4069  
4070  
4071  /**
4072   * Seconds duration setting with an advanced checkbox, that controls a additional
4073   * $name.'_adv' setting.
4074   *
4075   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4076   * @copyright 2014 The Open University
4077   */
4078  class admin_setting_configduration_with_advanced extends admin_setting_configduration {
4079      /**
4080       * Constructor
4081       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
4082       *                     or 'myplugin/mysetting' for ones in config_plugins.
4083       * @param string $visiblename localised name
4084       * @param string $description localised long description
4085       * @param array  $defaultsetting array of int value, and bool whether it is
4086       *                     is advanced by default.
4087       * @param int $defaultunit - day, week, etc. (in seconds)
4088       */
4089      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
4090          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $defaultunit);
4091          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
4092      }
4093  }
4094  
4095  
4096  /**
4097   * Used to validate a textarea used for ip addresses
4098   *
4099   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4100   * @copyright 2011 Petr Skoda (http://skodak.org)
4101   */
4102  class admin_setting_configiplist extends admin_setting_configtextarea {
4103  
4104      /**
4105       * Validate the contents of the textarea as IP addresses
4106       *
4107       * Used to validate a new line separated list of IP addresses collected from
4108       * a textarea control
4109       *
4110       * @param string $data A list of IP Addresses separated by new lines
4111       * @return mixed bool true for success or string:error on failure
4112       */
4113      public function validate($data) {
4114          if(!empty($data)) {
4115              $lines = explode("\n", $data);
4116          } else {
4117              return true;
4118          }
4119          $result = true;
4120          $badips = array();
4121          foreach ($lines as $line) {
4122              $tokens = explode('#', $line);
4123              $ip = trim($tokens[0]);
4124              if (empty($ip)) {
4125                  continue;
4126              }
4127              if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
4128                  preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
4129                  preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
4130              } else {
4131                  $result = false;
4132                  $badips[] = $ip;
4133              }
4134          }
4135          if($result) {
4136              return true;
4137          } else {
4138              return get_string('validateiperror', 'admin', join(', ', $badips));
4139          }
4140      }
4141  }
4142  
4143  /**
4144   * Used to validate a textarea used for domain names, wildcard domain names and IP addresses/ranges (both IPv4 and IPv6 format).
4145   *
4146   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4147   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4148   */
4149  class admin_setting_configmixedhostiplist extends admin_setting_configtextarea {
4150  
4151      /**
4152       * Validate the contents of the textarea as either IP addresses, domain name or wildcard domain name (RFC 4592).
4153       * Used to validate a new line separated list of entries collected from a textarea control.
4154       *
4155       * This setting provides support for internationalised domain names (IDNs), however, such UTF-8 names will be converted to
4156       * their ascii-compatible encoding (punycode) on save, and converted back to their UTF-8 representation when fetched
4157       * via the get_setting() method, which has been overriden.
4158       *
4159       * @param string $data A list of FQDNs, DNS wildcard format domains, and IP addresses, separated by new lines.
4160       * @return mixed bool true for success or string:error on failure
4161       */
4162      public function validate($data) {
4163          if (empty($data)) {
4164              return true;
4165          }
4166          $entries = explode("\n", $data);
4167          $badentries = [];
4168  
4169          foreach ($entries as $key => $entry) {
4170              $entry = trim($entry);
4171              if (empty($entry)) {
4172                  return get_string('validateemptylineerror', 'admin');
4173              }
4174  
4175              // Validate each string entry against the supported formats.
4176              if (\core\ip_utils::is_ip_address($entry) || \core\ip_utils::is_ipv6_range($entry)
4177                      || \core\ip_utils::is_ipv4_range($entry) || \core\ip_utils::is_domain_name($entry)
4178                      || \core\ip_utils::is_domain_matching_pattern($entry)) {
4179                  continue;
4180              }
4181  
4182              // Otherwise, the entry is invalid.
4183              $badentries[] = $entry;
4184          }
4185  
4186          if ($badentries) {
4187              return get_string('validateerrorlist', 'admin', join(', ', $badentries));
4188          }
4189          return true;
4190      }
4191  
4192      /**
4193       * Convert any lines containing international domain names (IDNs) to their ascii-compatible encoding (ACE).
4194       *
4195       * @param string $data the setting data, as sent from the web form.
4196       * @return string $data the setting data, with all IDNs converted (using punycode) to their ascii encoded version.
4197       */
4198      protected function ace_encode($data) {
4199          if (empty($data)) {
4200              return $data;
4201          }
4202          $entries = explode("\n", $data);
4203          foreach ($entries as $key => $entry) {
4204              $entry = trim($entry);
4205              // This regex matches any string that has non-ascii character.
4206              if (preg_match('/[^\x00-\x7f]/', $entry)) {
4207                  // If we can convert the unicode string to an idn, do so.
4208                  // Otherwise, leave the original unicode string alone and let the validation function handle it (it will fail).
4209                  $val = idn_to_ascii($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4210                  $entries[$key] = $val ? $val : $entry;
4211              }
4212          }
4213          return implode("\n", $entries);
4214      }
4215  
4216      /**
4217       * Decode any ascii-encoded domain names back to their utf-8 representation for display.
4218       *
4219       * @param string $data the setting data, as found in the database.
4220       * @return string $data the setting data, with all ascii-encoded IDNs decoded back to their utf-8 representation.
4221       */
4222      protected function ace_decode($data) {
4223          $entries = explode("\n", $data);
4224          foreach ($entries as $key => $entry) {
4225              $entry = trim($entry);
4226              if (strpos($entry, 'xn--') !== false) {
4227                  $entries[$key] = idn_to_utf8($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4228              }
4229          }
4230          return implode("\n", $entries);
4231      }
4232  
4233      /**
4234       * Override, providing utf8-decoding for ascii-encoded IDN strings.
4235       *
4236       * @return mixed returns punycode-converted setting string if successful, else null.
4237       */
4238      public function get_setting() {
4239          // Here, we need to decode any ascii-encoded IDNs back to their native, utf-8 representation.
4240          $data = $this->config_read($this->name);
4241          if (function_exists('idn_to_utf8') && !is_null($data)) {
4242              $data = $this->ace_decode($data);
4243          }
4244          return $data;
4245      }
4246  
4247      /**
4248       * Override, providing ascii-encoding for utf8 (native) IDN strings.
4249       *
4250       * @param string $data
4251       * @return string
4252       */
4253      public function write_setting($data) {
4254          if ($this->paramtype === PARAM_INT and $data === '') {
4255              // Do not complain if '' used instead of 0.
4256              $data = 0;
4257          }
4258  
4259          // Try to convert any non-ascii domains to ACE prior to validation - we can't modify anything in validate!
4260          if (function_exists('idn_to_ascii')) {
4261              $data = $this->ace_encode($data);
4262          }
4263  
4264          $validated = $this->validate($data);
4265          if ($validated !== true) {
4266              return $validated;
4267          }
4268          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
4269      }
4270  }
4271  
4272  /**
4273   * Used to validate a textarea used for port numbers.
4274   *
4275   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4276   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4277   */
4278  class admin_setting_configportlist extends admin_setting_configtextarea {
4279  
4280      /**
4281       * Validate the contents of the textarea as port numbers.
4282       * Used to validate a new line separated list of ports collected from a textarea control.
4283       *
4284       * @param string $data A list of ports separated by new lines
4285       * @return mixed bool true for success or string:error on failure
4286       */
4287      public function validate($data) {
4288          if (empty($data)) {
4289              return true;
4290          }
4291          $ports = explode("\n", $data);
4292          $badentries = [];
4293          foreach ($ports as $port) {
4294              $port = trim($port);
4295              if (empty($port)) {
4296                  return get_string('validateemptylineerror', 'admin');
4297              }
4298  
4299              // Is the string a valid integer number?
4300              if (strval(intval($port)) !== $port || intval($port) <= 0) {
4301                  $badentries[] = $port;
4302              }
4303          }
4304          if ($badentries) {
4305              return get_string('validateerrorlist', 'admin', $badentries);
4306          }
4307          return true;
4308      }
4309  }
4310  
4311  
4312  /**
4313   * An admin setting for selecting one or more users who have a capability
4314   * in the system context
4315   *
4316   * An admin setting for selecting one or more users, who have a particular capability
4317   * in the system context. Warning, make sure the list will never be too long. There is
4318   * no paging or searching of this list.
4319   *
4320   * To correctly get a list of users from this config setting, you need to call the
4321   * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
4322   *
4323   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4324   */
4325  class admin_setting_users_with_capability extends admin_setting_configmultiselect {
4326      /** @var string The capabilities name */
4327      protected $capability;
4328      /** @var int include admin users too */
4329      protected $includeadmins;
4330  
4331      /**
4332       * Constructor.
4333       *
4334       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
4335       * @param string $visiblename localised name
4336       * @param string $description localised long description
4337       * @param array $defaultsetting array of usernames
4338       * @param string $capability string capability name.
4339       * @param bool $includeadmins include administrators
4340       */
4341      function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
4342          $this->capability    = $capability;
4343          $this->includeadmins = $includeadmins;
4344          parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
4345      }
4346  
4347      /**
4348       * Load all of the uses who have the capability into choice array
4349       *
4350       * @return bool Always returns true
4351       */
4352      function load_choices() {
4353          if (is_array($this->choices)) {
4354              return true;
4355          }
4356          list($sort, $sortparams) = users_order_by_sql('u');
4357          if (!empty($sortparams)) {
4358              throw new coding_exception('users_order_by_sql returned some query parameters. ' .
4359                      'This is unexpected, and a problem because there is no way to pass these ' .
4360                      'parameters to get_users_by_capability. See MDL-34657.');
4361          }
4362          $userfieldsapi = \core_user\fields::for_name();
4363          $userfields = 'u.id, u.username, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects;
4364          $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort);
4365          $this->choices = array(
4366              '$@NONE@$' => get_string('nobody'),
4367              '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
4368          );
4369          if ($this->includeadmins) {
4370              $admins = get_admins();
4371              foreach ($admins as $user) {
4372                  $this->choices[$user->id] = fullname($user);
4373              }
4374          }
4375          if (is_array($users)) {
4376              foreach ($users as $user) {
4377                  $this->choices[$user->id] = fullname($user);
4378              }
4379          }
4380          return true;
4381      }
4382  
4383      /**
4384       * Returns the default setting for class
4385       *
4386       * @return mixed Array, or string. Empty string if no default
4387       */
4388      public function get_defaultsetting() {
4389          $this->load_choices();
4390          $defaultsetting = parent::get_defaultsetting();
4391          if (empty($defaultsetting)) {
4392              return array('$@NONE@$');
4393          } else if (array_key_exists($defaultsetting, $this->choices)) {
4394                  return $defaultsetting;
4395              } else {
4396                  return '';
4397              }
4398      }
4399  
4400      /**
4401       * Returns the current setting
4402       *
4403       * @return mixed array or string
4404       */
4405      public function get_setting() {
4406          $result = parent::get_setting();
4407          if ($result === null) {
4408              // this is necessary for settings upgrade
4409              return null;
4410          }
4411          if (empty($result)) {
4412              $result = array('$@NONE@$');
4413          }
4414          return $result;
4415      }
4416  
4417      /**
4418       * Save the chosen setting provided as $data
4419       *
4420       * @param array $data
4421       * @return mixed string or array
4422       */
4423      public function write_setting($data) {
4424      // If all is selected, remove any explicit options.
4425          if (in_array('$@ALL@$', $data)) {
4426              $data = array('$@ALL@$');
4427          }
4428          // None never needs to be written to the DB.
4429          if (in_array('$@NONE@$', $data)) {
4430              unset($data[array_search('$@NONE@$', $data)]);
4431          }
4432          return parent::write_setting($data);
4433      }
4434  }
4435  
4436  
4437  /**
4438   * Special checkbox for calendar - resets SESSION vars.
4439   *
4440   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4441   */
4442  class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
4443      /**
4444       * Calls the parent::__construct with default values
4445       *
4446       * name =>  calendar_adminseesall
4447       * visiblename => get_string('adminseesall', 'admin')
4448       * description => get_string('helpadminseesall', 'admin')
4449       * defaultsetting => 0
4450       */
4451      public function __construct() {
4452          parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
4453              get_string('helpadminseesall', 'admin'), '0');
4454      }
4455  
4456      /**
4457       * Stores the setting passed in $data
4458       *
4459       * @param mixed gets converted to string for comparison
4460       * @return string empty string or error message
4461       */
4462      public function write_setting($data) {
4463          global $SESSION;
4464          return parent::write_setting($data);
4465      }
4466  }
4467  
4468  /**
4469   * Special select for settings that are altered in setup.php and can not be altered on the fly
4470   *
4471   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4472   */
4473  class admin_setting_special_selectsetup extends admin_setting_configselect {
4474      /**
4475       * Reads the setting directly from the database
4476       *
4477       * @return mixed
4478       */
4479      public function get_setting() {
4480      // read directly from db!
4481          return get_config(NULL, $this->name);
4482      }
4483  
4484      /**
4485       * Save the setting passed in $data
4486       *
4487       * @param string $data The setting to save
4488       * @return string empty or error message
4489       */
4490      public function write_setting($data) {
4491          global $CFG;
4492          // do not change active CFG setting!
4493          $current = $CFG->{$this->name};
4494          $result = parent::write_setting($data);
4495          $CFG->{$this->name} = $current;
4496          return $result;
4497      }
4498  }
4499  
4500  
4501  /**
4502   * Special select for frontpage - stores data in course table
4503   *
4504   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4505   */
4506  class admin_setting_sitesetselect extends admin_setting_configselect {
4507      /**
4508       * Returns the site name for the selected site
4509       *
4510       * @see get_site()
4511       * @return string The site name of the selected site
4512       */
4513      public function get_setting() {
4514          $site = course_get_format(get_site())->get_course();
4515          return $site->{$this->name};
4516      }
4517  
4518      /**
4519       * Updates the database and save the setting
4520       *
4521       * @param string data
4522       * @return string empty or error message
4523       */
4524      public function write_setting($data) {
4525          global $DB, $SITE, $COURSE;
4526          if (!in_array($data, array_keys($this->choices))) {
4527              return get_string('errorsetting', 'admin');
4528          }
4529          $record = new stdClass();
4530          $record->id           = SITEID;
4531          $temp                 = $this->name;
4532          $record->$temp        = $data;
4533          $record->timemodified = time();
4534  
4535          course_get_format($SITE)->update_course_format_options($record);
4536          $DB->update_record('course', $record);
4537  
4538          // Reset caches.
4539          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4540          if ($SITE->id == $COURSE->id) {
4541              $COURSE = $SITE;
4542          }
4543          core_courseformat\base::reset_course_cache($SITE->id);
4544  
4545          return '';
4546  
4547      }
4548  }
4549  
4550  
4551  /**
4552   * Select for blog's bloglevel setting: if set to 0, will set blog_menu
4553   * block to hidden.
4554   *
4555   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4556   */
4557  class admin_setting_bloglevel extends admin_setting_configselect {
4558      /**
4559       * Updates the database and save the setting
4560       *
4561       * @param string data
4562       * @return string empty or error message
4563       */
4564      public function write_setting($data) {
4565          global $DB, $CFG;
4566          if ($data == 0) {
4567              $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
4568              foreach ($blogblocks as $block) {
4569                  $DB->set_field('block', 'visible', 0, array('id' => $block->id));
4570              }
4571          } else {
4572              // reenable all blocks only when switching from disabled blogs
4573              if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
4574                  $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
4575                  foreach ($blogblocks as $block) {
4576                      $DB->set_field('block', 'visible', 1, array('id' => $block->id));
4577                  }
4578              }
4579          }
4580          return parent::write_setting($data);
4581      }
4582  }
4583  
4584  
4585  /**
4586   * Special select - lists on the frontpage - hacky
4587   *
4588   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4589   */
4590  class admin_setting_courselist_frontpage extends admin_setting {
4591      /** @var array Array of choices value=>label */
4592      public $choices;
4593  
4594      /**
4595       * Construct override, requires one param
4596       *
4597       * @param bool $loggedin Is the user logged in
4598       */
4599      public function __construct($loggedin) {
4600          global $CFG;
4601          require_once($CFG->dirroot.'/course/lib.php');
4602          $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
4603          $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
4604          $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
4605          $defaults    = array(FRONTPAGEALLCOURSELIST);
4606          parent::__construct($name, $visiblename, $description, $defaults);
4607      }
4608  
4609      /**
4610       * Loads the choices available
4611       *
4612       * @return bool always returns true
4613       */
4614      public function load_choices() {
4615          if (is_array($this->choices)) {
4616              return true;
4617          }
4618          $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
4619              FRONTPAGEALLCOURSELIST => get_string('frontpagecourselist'),
4620              FRONTPAGEENROLLEDCOURSELIST => get_string('frontpageenrolledcourselist'),
4621              FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
4622              FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
4623              FRONTPAGECOURSESEARCH  => get_string('frontpagecoursesearch'),
4624              'none'                 => get_string('none'));
4625          if ($this->name === 'frontpage') {
4626              unset($this->choices[FRONTPAGEENROLLEDCOURSELIST]);
4627          }
4628          return true;
4629      }
4630  
4631      /**
4632       * Returns the selected settings
4633       *
4634       * @param mixed array or setting or null
4635       */
4636      public function get_setting() {
4637          $result = $this->config_read($this->name);
4638          if (is_null($result)) {
4639              return NULL;
4640          }
4641          if ($result === '') {
4642              return array();
4643          }
4644          return explode(',', $result);
4645      }
4646  
4647      /**
4648       * Save the selected options
4649       *
4650       * @param array $data
4651       * @return mixed empty string (data is not an array) or bool true=success false=failure
4652       */
4653      public function write_setting($data) {
4654          if (!is_array($data)) {
4655              return '';
4656          }
4657          $this->load_choices();
4658          $save = array();
4659          foreach($data as $datum) {
4660              if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
4661                  continue;
4662              }
4663              $save[$datum] = $datum; // no duplicates
4664          }
4665          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
4666      }
4667  
4668      /**
4669       * Return XHTML select field and wrapping div
4670       *
4671       * @todo Add vartype handling to make sure $data is an array
4672       * @param array $data Array of elements to select by default
4673       * @return string XHTML select field and wrapping div
4674       */
4675      public function output_html($data, $query='') {
4676          global $OUTPUT;
4677  
4678          $this->load_choices();
4679          $currentsetting = array();
4680          foreach ($data as $key) {
4681              if ($key != 'none' and array_key_exists($key, $this->choices)) {
4682                  $currentsetting[] = $key; // already selected first
4683              }
4684          }
4685  
4686          $context = (object) [
4687              'id' => $this->get_id(),
4688              'name' => $this->get_full_name(),
4689          ];
4690  
4691          $options = $this->choices;
4692          $selects = [];
4693          for ($i = 0; $i < count($this->choices) - 1; $i++) {
4694              if (!array_key_exists($i, $currentsetting)) {
4695                  $currentsetting[$i] = 'none';
4696              }
4697              $selects[] = [
4698                  'key' => $i,
4699                  'options' => array_map(function($option) use ($options, $currentsetting, $i) {
4700                      return [
4701                          'name' => $options[$option],
4702                          'value' => $option,
4703                          'selected' => $currentsetting[$i] == $option
4704                      ];
4705                  }, array_keys($options))
4706              ];
4707          }
4708          $context->selects = $selects;
4709  
4710          $element = $OUTPUT->render_from_template('core_admin/setting_courselist_frontpage', $context);
4711  
4712          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
4713      }
4714  }
4715  
4716  
4717  /**
4718   * Special checkbox for frontpage - stores data in course table
4719   *
4720   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4721   */
4722  class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
4723      /**
4724       * Returns the current sites name
4725       *
4726       * @return string
4727       */
4728      public function get_setting() {
4729          $site = course_get_format(get_site())->get_course();
4730          return $site->{$this->name};
4731      }
4732  
4733      /**
4734       * Save the selected setting
4735       *
4736       * @param string $data The selected site
4737       * @return string empty string or error message
4738       */
4739      public function write_setting($data) {
4740          global $DB, $SITE, $COURSE;
4741          $record = new stdClass();
4742          $record->id            = $SITE->id;
4743          $record->{$this->name} = ($data == '1' ? 1 : 0);
4744          $record->timemodified  = time();
4745  
4746          course_get_format($SITE)->update_course_format_options($record);
4747          $DB->update_record('course', $record);
4748  
4749          // Reset caches.
4750          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4751          if ($SITE->id == $COURSE->id) {
4752              $COURSE = $SITE;
4753          }
4754          core_courseformat\base::reset_course_cache($SITE->id);
4755  
4756          return '';
4757      }
4758  }
4759  
4760  /**
4761   * Special text for frontpage - stores data in course table.
4762   * Empty string means not set here. Manual setting is required.
4763   *
4764   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4765   */
4766  class admin_setting_sitesettext extends admin_setting_configtext {
4767  
4768      /**
4769       * Constructor.
4770       */
4771      public function __construct() {
4772          call_user_func_array(['parent', '__construct'], func_get_args());
4773          $this->set_force_ltr(false);
4774      }
4775  
4776      /**
4777       * Return the current setting
4778       *
4779       * @return mixed string or null
4780       */
4781      public function get_setting() {
4782          $site = course_get_format(get_site())->get_course();
4783          return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
4784      }
4785  
4786      /**
4787       * Validate the selected data
4788       *
4789       * @param string $data The selected value to validate
4790       * @return mixed true or message string
4791       */
4792      public function validate($data) {
4793          global $DB, $SITE;
4794          $cleaned = clean_param($data, PARAM_TEXT);
4795          if ($cleaned === '') {
4796              return get_string('required');
4797          }
4798          if ($this->name ==='shortname' &&
4799                  $DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data, $SITE->id))) {
4800              return get_string('shortnametaken', 'error', $data);
4801          }
4802          if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
4803              return true;
4804          } else {
4805              return get_string('validateerror', 'admin');
4806          }
4807      }
4808  
4809      /**
4810       * Save the selected setting
4811       *
4812       * @param string $data The selected value
4813       * @return string empty or error message
4814       */
4815      public function write_setting($data) {
4816          global $DB, $SITE, $COURSE;
4817          $data = trim($data);
4818          $validated = $this->validate($data);
4819          if ($validated !== true) {
4820              return $validated;
4821          }
4822  
4823          $record = new stdClass();
4824          $record->id            = $SITE->id;
4825          $record->{$this->name} = $data;
4826          $record->timemodified  = time();
4827  
4828          course_get_format($SITE)->update_course_format_options($record);
4829          $DB->update_record('course', $record);
4830  
4831          // Reset caches.
4832          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4833          if ($SITE->id == $COURSE->id) {
4834              $COURSE = $SITE;
4835          }
4836          core_courseformat\base::reset_course_cache($SITE->id);
4837  
4838          return '';
4839      }
4840  }
4841  
4842  
4843  /**
4844   * This type of field should be used for mandatory config settings.
4845   *
4846   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4847   */
4848  class admin_setting_requiredtext extends admin_setting_configtext {
4849  
4850      /**
4851       * Validate data before storage.
4852       *
4853       * @param string $data The string to be validated.
4854       * @return bool|string true for success or error string if invalid.
4855       */
4856      public function validate($data) {
4857          $cleaned = clean_param($data, PARAM_TEXT);
4858          if ($cleaned === '') {
4859              return get_string('required');
4860          }
4861  
4862          return parent::validate($data);
4863      }
4864  }
4865  
4866  /**
4867   * Special text editor for site description.
4868   *
4869   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4870   */
4871  class admin_setting_special_frontpagedesc extends admin_setting_confightmleditor {
4872  
4873      /**
4874       * Calls parent::__construct with specific arguments
4875       */
4876      public function __construct() {
4877          parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), null,
4878              PARAM_RAW, 60, 15);
4879      }
4880  
4881      /**
4882       * Return the current setting
4883       * @return string The current setting
4884       */
4885      public function get_setting() {
4886          $site = course_get_format(get_site())->get_course();
4887          return $site->{$this->name};
4888      }
4889  
4890      /**
4891       * Save the new setting
4892       *
4893       * @param string $data The new value to save
4894       * @return string empty or error message
4895       */
4896      public function write_setting($data) {
4897          global $DB, $SITE, $COURSE;
4898          $record = new stdClass();
4899          $record->id            = $SITE->id;
4900          $record->{$this->name} = $data;
4901          $record->timemodified  = time();
4902  
4903          course_get_format($SITE)->update_course_format_options($record);
4904          $DB->update_record('course', $record);
4905  
4906          // Reset caches.
4907          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4908          if ($SITE->id == $COURSE->id) {
4909              $COURSE = $SITE;
4910          }
4911          core_courseformat\base::reset_course_cache($SITE->id);
4912  
4913          return '';
4914      }
4915  }
4916  
4917  
4918  /**
4919   * Administration interface for emoticon_manager settings.
4920   *
4921   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4922   */
4923  class admin_setting_emoticons extends admin_setting {
4924  
4925      /**
4926       * Calls parent::__construct with specific args
4927       */
4928      public function __construct() {
4929          global $CFG;
4930  
4931          $manager = get_emoticon_manager();
4932          $defaults = $this->prepare_form_data($manager->default_emoticons());
4933          parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
4934      }
4935  
4936      /**
4937       * Return the current setting(s)
4938       *
4939       * @return array Current settings array
4940       */
4941      public function get_setting() {
4942          global $CFG;
4943  
4944          $manager = get_emoticon_manager();
4945  
4946          $config = $this->config_read($this->name);
4947          if (is_null($config)) {
4948              return null;
4949          }
4950  
4951          $config = $manager->decode_stored_config($config);
4952          if (is_null($config)) {
4953              return null;
4954          }
4955  
4956          return $this->prepare_form_data($config);
4957      }
4958  
4959      /**
4960       * Save selected settings
4961       *
4962       * @param array $data Array of settings to save
4963       * @return bool
4964       */
4965      public function write_setting($data) {
4966  
4967          $manager = get_emoticon_manager();
4968          $emoticons = $this->process_form_data($data);
4969  
4970          if ($emoticons === false) {
4971              return false;
4972          }
4973  
4974          if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
4975              return ''; // success
4976          } else {
4977              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
4978          }
4979      }
4980  
4981      /**
4982       * Return XHTML field(s) for options
4983       *
4984       * @param array $data Array of options to set in HTML
4985       * @return string XHTML string for the fields and wrapping div(s)
4986       */
4987      public function output_html($data, $query='') {
4988          global $OUTPUT;
4989  
4990          $context = (object) [
4991              'name' => $this->get_full_name(),
4992              'emoticons' => [],
4993              'forceltr' => true,
4994          ];
4995  
4996          $i = 0;
4997          foreach ($data as $field => $value) {
4998  
4999              // When $i == 0: text.
5000              // When $i == 1: imagename.
5001              // When $i == 2: imagecomponent.
5002              // When $i == 3: altidentifier.
5003              // When $i == 4: altcomponent.
5004              $fields[$i] = (object) [
5005                  'field' => $field,
5006                  'value' => $value,
5007                  'index' => $i
5008              ];
5009              $i++;
5010  
5011              if ($i > 4) {
5012                  $icon = null;
5013                  if (!empty($fields[1]->value)) {
5014                      if (get_string_manager()->string_exists($fields[3]->value, $fields[4]->value)) {
5015                          $alt = get_string($fields[3]->value, $fields[4]->value);
5016                      } else {
5017                          $alt = $fields[0]->value;
5018                      }
5019                      $icon = new pix_emoticon($fields[1]->value, $alt, $fields[2]->value);
5020                  }
5021                  $context->emoticons[] = [
5022                      'fields' => $fields,
5023                      'icon' => $icon ? $icon->export_for_template($OUTPUT) : null
5024                  ];
5025                  $fields = [];
5026                  $i = 0;
5027              }
5028          }
5029  
5030          $context->reseturl = new moodle_url('/admin/resetemoticons.php');
5031          $element = $OUTPUT->render_from_template('core_admin/setting_emoticons', $context);
5032          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
5033      }
5034  
5035      /**
5036       * Converts the array of emoticon objects provided by {@see emoticon_manager} into admin settings form data
5037       *
5038       * @see self::process_form_data()
5039       * @param array $emoticons array of emoticon objects as returned by {@see emoticon_manager}
5040       * @return array of form fields and their values
5041       */
5042      protected function prepare_form_data(array $emoticons) {
5043  
5044          $form = array();
5045          $i = 0;
5046          foreach ($emoticons as $emoticon) {
5047              $form['text'.$i]            = $emoticon->text;
5048              $form['imagename'.$i]       = $emoticon->imagename;
5049              $form['imagecomponent'.$i]  = $emoticon->imagecomponent;
5050              $form['altidentifier'.$i]   = $emoticon->altidentifier;
5051              $form['altcomponent'.$i]    = $emoticon->altcomponent;
5052              $i++;
5053          }
5054          // add one more blank field set for new object
5055          $form['text'.$i]            = '';
5056          $form['imagename'.$i]       = '';
5057          $form['imagecomponent'.$i]  = '';
5058          $form['altidentifier'.$i]   = '';
5059          $form['altcomponent'.$i]    = '';
5060  
5061          return $form;
5062      }
5063  
5064      /**
5065       * Converts the data from admin settings form into an array of emoticon objects
5066       *
5067       * @see self::prepare_form_data()
5068       * @param array $data array of admin form fields and values
5069       * @return false|array of emoticon objects
5070       */
5071      protected function process_form_data(array $form) {
5072  
5073          $count = count($form); // number of form field values
5074  
5075          if ($count % 5) {
5076              // we must get five fields per emoticon object
5077              return false;
5078          }
5079  
5080          $emoticons = array();
5081          for ($i = 0; $i < $count / 5; $i++) {
5082              $emoticon                   = new stdClass();
5083              $emoticon->text             = clean_param(trim($form['text'.$i]), PARAM_NOTAGS);
5084              $emoticon->imagename        = clean_param(trim($form['imagename'.$i]), PARAM_PATH);
5085              $emoticon->imagecomponent   = clean_param(trim($form['imagecomponent'.$i]), PARAM_COMPONENT);
5086              $emoticon->altidentifier    = clean_param(trim($form['altidentifier'.$i]), PARAM_STRINGID);
5087              $emoticon->altcomponent     = clean_param(trim($form['altcomponent'.$i]), PARAM_COMPONENT);
5088  
5089              if (strpos($emoticon->text, ':/') !== false or strpos($emoticon->text, '//') !== false) {
5090                  // prevent from breaking http://url.addresses by accident
5091                  $emoticon->text = '';
5092              }
5093  
5094              if (strlen($emoticon->text) < 2) {
5095                  // do not allow single character emoticons
5096                  $emoticon->text = '';
5097              }
5098  
5099              if (preg_match('/^[a-zA-Z]+[a-zA-Z0-9]*$/', $emoticon->text)) {
5100                  // emoticon text must contain some non-alphanumeric character to prevent
5101                  // breaking HTML tags
5102                  $emoticon->text = '';
5103              }
5104  
5105              if ($emoticon->text !== '' and $emoticon->imagename !== '' and $emoticon->imagecomponent !== '') {
5106                  $emoticons[] = $emoticon;
5107              }
5108          }
5109          return $emoticons;
5110      }
5111  
5112  }
5113  
5114  
5115  /**
5116   * Special setting for limiting of the list of available languages.
5117   *
5118   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5119   */
5120  class admin_setting_langlist extends admin_setting_configtext {
5121      /**
5122       * Calls parent::__construct with specific arguments
5123       */
5124      public function __construct() {
5125          parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
5126      }
5127  
5128      /**
5129       * Validate that each language identifier exists on the site
5130       *
5131       * @param string $data
5132       * @return bool|string True if validation successful, otherwise error string
5133       */
5134      public function validate($data) {
5135          $parentcheck = parent::validate($data);
5136          if ($parentcheck !== true) {
5137              return $parentcheck;
5138          }
5139  
5140          if ($data === '') {
5141              return true;
5142          }
5143  
5144          // Normalize language identifiers.
5145          $langcodes = array_map('trim', explode(',', $data));
5146          foreach ($langcodes as $langcode) {
5147              // If the langcode contains optional alias, split it out.
5148              [$langcode, ] = preg_split('/\s*\|\s*/', $langcode, 2);
5149  
5150              if (!get_string_manager()->translation_exists($langcode)) {
5151                  return get_string('invalidlanguagecode', 'error', $langcode);
5152              }
5153          }
5154  
5155          return true;
5156      }
5157  
5158      /**
5159       * Save the new setting
5160       *
5161       * @param string $data The new setting
5162       * @return bool
5163       */
5164      public function write_setting($data) {
5165          $return = parent::write_setting($data);
5166          get_string_manager()->reset_caches();
5167          return $return;
5168      }
5169  }
5170  
5171  
5172  /**
5173   * Allows to specify comma separated list of known country codes.
5174   *
5175   * This is a simple subclass of the plain input text field with added validation so that all the codes are actually
5176   * known codes.
5177   *
5178   * @package     core
5179   * @category    admin
5180   * @copyright   2020 David Mudrák <david@moodle.com>
5181   * @license     https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5182   */
5183  class admin_setting_countrycodes extends admin_setting_configtext {
5184  
5185      /**
5186       * Construct the instance of the setting.
5187       *
5188       * @param string $name Name of the admin setting such as 'allcountrycodes' or 'myplugin/countries'.
5189       * @param lang_string|string $visiblename Language string with the field label text.
5190       * @param lang_string|string $description Language string with the field description text.
5191       * @param string $defaultsetting Default value of the setting.
5192       * @param int $size Input text field size.
5193       */
5194      public function __construct($name, $visiblename, $description, $defaultsetting = '', $size = null) {
5195          parent::__construct($name, $visiblename, $description, $defaultsetting, '/^(?:\w+(?:,\w+)*)?$/', $size);
5196      }
5197  
5198      /**
5199       * Validate the setting value before storing it.
5200       *
5201       * The value is first validated through custom regex so that it is a word consisting of letters, numbers or underscore; or
5202       * a comma separated list of such words.
5203       *
5204       * @param string $data Value inserted into the setting field.
5205       * @return bool|string True if the value is OK, error string otherwise.
5206       */
5207      public function validate($data) {
5208  
5209          $parentcheck = parent::validate($data);
5210  
5211          if ($parentcheck !== true) {
5212              return $parentcheck;
5213          }
5214  
5215          if ($data === '') {
5216              return true;
5217          }
5218  
5219          $allcountries = get_string_manager()->get_list_of_countries(true);
5220  
5221          foreach (explode(',', $data) as $code) {
5222              if (!isset($allcountries[$code])) {
5223                  return get_string('invalidcountrycode', 'core_error', $code);
5224              }
5225          }
5226  
5227          return true;
5228      }
5229  }
5230  
5231  
5232  /**
5233   * Selection of one of the recognised countries using the list
5234   * returned by {@link get_list_of_countries()}.
5235   *
5236   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5237   */
5238  class admin_settings_country_select extends admin_setting_configselect {
5239      protected $includeall;
5240      public function __construct($name, $visiblename, $description, $defaultsetting, $includeall=false) {
5241          $this->includeall = $includeall;
5242          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
5243      }
5244  
5245      /**
5246       * Lazy-load the available choices for the select box
5247       */
5248      public function load_choices() {
5249          global $CFG;
5250          if (is_array($this->choices)) {
5251              return true;
5252          }
5253          $this->choices = array_merge(
5254                  array('0' => get_string('choosedots')),
5255                  get_string_manager()->get_list_of_countries($this->includeall));
5256          return true;
5257      }
5258  }
5259  
5260  
5261  /**
5262   * admin_setting_configselect for the default number of sections in a course,
5263   * simply so we can lazy-load the choices.
5264   *
5265   * @copyright 2011 The Open University
5266   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5267   */
5268  class admin_settings_num_course_sections extends admin_setting_configselect {
5269      public function __construct($name, $visiblename, $description, $defaultsetting) {
5270          parent::__construct($name, $visiblename, $description, $defaultsetting, array());
5271      }
5272  
5273      /** Lazy-load the available choices for the select box */
5274      public function load_choices() {
5275          $max = get_config('moodlecourse', 'maxsections');
5276          if (!isset($max) || !is_numeric($max)) {
5277              $max = 52;
5278          }
5279          for ($i = 0; $i <= $max; $i++) {
5280              $this->choices[$i] = "$i";
5281          }
5282          return true;
5283      }
5284  }
5285  
5286  
5287  /**
5288   * Course category selection
5289   *
5290   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5291   */
5292  class admin_settings_coursecat_select extends admin_setting_configselect_autocomplete {
5293      /**
5294       * Calls parent::__construct with specific arguments
5295       */
5296      public function __construct($name, $visiblename, $description, $defaultsetting = 1) {
5297          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices = null);
5298      }
5299  
5300      /**
5301       * Load the available choices for the select box
5302       *
5303       * @return bool
5304       */
5305      public function load_choices() {
5306          if (is_array($this->choices)) {
5307              return true;
5308          }
5309          $this->choices = core_course_category::make_categories_list('', 0, ' / ');
5310          return true;
5311      }
5312  }
5313  
5314  
5315  /**
5316   * Special control for selecting days to backup
5317   *
5318   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5319   */
5320  class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
5321      /**
5322       * Calls parent::__construct with specific arguments
5323       */
5324      public function __construct() {
5325          parent::__construct('backup_auto_weekdays', get_string('automatedbackupschedule','backup'), get_string('automatedbackupschedulehelp','backup'), array(), NULL);
5326          $this->plugin = 'backup';
5327      }
5328  
5329      /**
5330       * Load the available choices for the select box
5331       *
5332       * @return bool Always returns true
5333       */
5334      public function load_choices() {
5335          if (is_array($this->choices)) {
5336              return true;
5337          }
5338          $this->choices = array();
5339          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5340          foreach ($days as $day) {
5341              $this->choices[$day] = get_string($day, 'calendar');
5342          }
5343          return true;
5344      }
5345  }
5346  
5347  /**
5348   * Special setting for backup auto destination.
5349   *
5350   * @package    core
5351   * @subpackage admin
5352   * @copyright  2014 Frédéric Massart - FMCorz.net
5353   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5354   */
5355  class admin_setting_special_backup_auto_destination extends admin_setting_configdirectory {
5356  
5357      /**
5358       * Calls parent::__construct with specific arguments.
5359       */
5360      public function __construct() {
5361          parent::__construct('backup/backup_auto_destination', new lang_string('saveto'), new lang_string('backupsavetohelp'), '');
5362      }
5363  
5364      /**
5365       * Check if the directory must be set, depending on backup/backup_auto_storage.
5366       *
5367       * Note: backup/backup_auto_storage must be specified BEFORE this setting otherwise
5368       * there will be conflicts if this validation happens before the other one.
5369       *
5370       * @param string $data Form data.
5371       * @return string Empty when no errors.
5372       */
5373      public function write_setting($data) {
5374          $storage = (int) get_config('backup', 'backup_auto_storage');
5375          if ($storage !== 0) {
5376              if (empty($data) || !file_exists($data) || !is_dir($data) || !is_writable($data) ) {
5377                  // The directory must exist and be writable.
5378                  return get_string('backuperrorinvaliddestination');
5379              }
5380          }
5381          return parent::write_setting($data);
5382      }
5383  }
5384  
5385  
5386  /**
5387   * Special debug setting
5388   *
5389   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5390   */
5391  class admin_setting_special_debug extends admin_setting_configselect {
5392      /**
5393       * Calls parent::__construct with specific arguments
5394       */
5395      public function __construct() {
5396          parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
5397      }
5398  
5399      /**
5400       * Load the available choices for the select box
5401       *
5402       * @return bool
5403       */
5404      public function load_choices() {
5405          if (is_array($this->choices)) {
5406              return true;
5407          }
5408          $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
5409              DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
5410              DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
5411              DEBUG_ALL       => get_string('debugall', 'admin'),
5412              DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
5413          return true;
5414      }
5415  }
5416  
5417  
5418  /**
5419   * Special admin control
5420   *
5421   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5422   */
5423  class admin_setting_special_calendar_weekend extends admin_setting {
5424      /**
5425       * Calls parent::__construct with specific arguments
5426       */
5427      public function __construct() {
5428          $name = 'calendar_weekend';
5429          $visiblename = get_string('calendar_weekend', 'admin');
5430          $description = get_string('helpweekenddays', 'admin');
5431          $default = array ('0', '6'); // Saturdays and Sundays
5432          parent::__construct($name, $visiblename, $description, $default);
5433      }
5434  
5435      /**
5436       * Gets the current settings as an array
5437       *
5438       * @return mixed Null if none, else array of settings
5439       */
5440      public function get_setting() {
5441          $result = $this->config_read($this->name);
5442          if (is_null($result)) {
5443              return NULL;
5444          }
5445          if ($result === '') {
5446              return array();
5447          }
5448          $settings = array();
5449          for ($i=0; $i<7; $i++) {
5450              if ($result & (1 << $i)) {
5451                  $settings[] = $i;
5452              }
5453          }
5454          return $settings;
5455      }
5456  
5457      /**
5458       * Save the new settings
5459       *
5460       * @param array $data Array of new settings
5461       * @return bool
5462       */
5463      public function write_setting($data) {
5464          if (!is_array($data)) {
5465              return '';
5466          }
5467          unset($data['xxxxx']);
5468          $result = 0;
5469          foreach($data as $index) {
5470              $result |= 1 << $index;
5471          }
5472          return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
5473      }
5474  
5475      /**
5476       * Return XHTML to display the control
5477       *
5478       * @param array $data array of selected days
5479       * @param string $query
5480       * @return string XHTML for display (field + wrapping div(s)
5481       */
5482      public function output_html($data, $query='') {
5483          global $OUTPUT;
5484  
5485          // The order matters very much because of the implied numeric keys.
5486          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5487          $context = (object) [
5488              'name' => $this->get_full_name(),
5489              'id' => $this->get_id(),
5490              'days' => array_map(function($index) use ($days, $data) {
5491                  return [
5492                      'index' => $index,
5493                      'label' => get_string($days[$index], 'calendar'),
5494                      'checked' => in_array($index, $data)
5495                  ];
5496              }, array_keys($days))
5497          ];
5498  
5499          $element = $OUTPUT->render_from_template('core_admin/setting_special_calendar_weekend', $context);
5500  
5501          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
5502  
5503      }
5504  }
5505  
5506  
5507  /**
5508   * Admin setting that allows a user to pick a behaviour.
5509   *
5510   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5511   */
5512  class admin_setting_question_behaviour extends admin_setting_configselect {
5513      /**
5514       * @param string $name name of config variable
5515       * @param string $visiblename display name
5516       * @param string $description description
5517       * @param string $default default.
5518       */
5519      public function __construct($name, $visiblename, $description, $default) {
5520          parent::__construct($name, $visiblename, $description, $default, null);
5521      }
5522  
5523      /**
5524       * Load list of behaviours as choices
5525       * @return bool true => success, false => error.
5526       */
5527      public function load_choices() {
5528          global $CFG;
5529          require_once($CFG->dirroot . '/question/engine/lib.php');
5530          $this->choices = question_engine::get_behaviour_options('');
5531          return true;
5532      }
5533  }
5534  
5535  
5536  /**
5537   * Admin setting that allows a user to pick appropriate roles for something.
5538   *
5539   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5540   */
5541  class admin_setting_pickroles extends admin_setting_configmulticheckbox {
5542      /** @var array Array of capabilities which identify roles */
5543      private $types;
5544  
5545      /**
5546       * @param string $name Name of config variable
5547       * @param string $visiblename Display name
5548       * @param string $description Description
5549       * @param array $types Array of archetypes which identify
5550       *              roles that will be enabled by default.
5551       */
5552      public function __construct($name, $visiblename, $description, $types) {
5553          parent::__construct($name, $visiblename, $description, NULL, NULL);
5554          $this->types = $types;
5555      }
5556  
5557      /**
5558       * Load roles as choices
5559       *
5560       * @return bool true=>success, false=>error
5561       */
5562      public function load_choices() {
5563          global $CFG, $DB;
5564          if (during_initial_install()) {
5565              return false;
5566          }
5567          if (is_array($this->choices)) {
5568              return true;
5569          }
5570          if ($roles = get_all_roles()) {
5571              $this->choices = role_fix_names($roles, null, ROLENAME_ORIGINAL, true);
5572              return true;
5573          } else {
5574              return false;
5575          }
5576      }
5577  
5578      /**
5579       * Return the default setting for this control
5580       *
5581       * @return array Array of default settings
5582       */
5583      public function get_defaultsetting() {
5584          global $CFG;
5585  
5586          if (during_initial_install()) {
5587              return null;
5588          }
5589          $result = array();
5590          foreach($this->types as $archetype) {
5591              if ($caproles = get_archetype_roles($archetype)) {
5592                  foreach ($caproles as $caprole) {
5593                      $result[$caprole->id] = 1;
5594                  }
5595              }
5596          }
5597          return $result;
5598      }
5599  }
5600  
5601  
5602  /**
5603   * Admin setting that is a list of installed filter plugins.
5604   *
5605   * @copyright 2015 The Open University
5606   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5607   */
5608  class admin_setting_pickfilters extends admin_setting_configmulticheckbox {
5609  
5610      /**
5611       * Constructor
5612       *
5613       * @param string $name unique ascii name, either 'mysetting' for settings
5614       *      that in config, or 'myplugin/mysetting' for ones in config_plugins.
5615       * @param string $visiblename localised name
5616       * @param string $description localised long description
5617       * @param array $default the default. E.g. array('urltolink' => 1, 'emoticons' => 1)
5618       */
5619      public function __construct($name, $visiblename, $description, $default) {
5620          if (empty($default)) {
5621              $default = array();
5622          }
5623          $this->load_choices();
5624          foreach ($default as $plugin) {
5625              if (!isset($this->choices[$plugin])) {
5626                  unset($default[$plugin]);
5627              }
5628          }
5629          parent::__construct($name, $visiblename, $description, $default, null);
5630      }
5631  
5632      public function load_choices() {
5633          if (is_array($this->choices)) {
5634              return true;
5635          }
5636          $this->choices = array();
5637  
5638          foreach (core_component::get_plugin_list('filter') as $plugin => $unused) {
5639              $this->choices[$plugin] = filter_get_name($plugin);
5640          }
5641          return true;
5642      }
5643  }
5644  
5645  
5646  /**
5647   * Text field with an advanced checkbox, that controls a additional $name.'_adv' setting.
5648   *
5649   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5650   */
5651  class admin_setting_configtext_with_advanced extends admin_setting_configtext {
5652      /**
5653       * Constructor
5654       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5655       * @param string $visiblename localised
5656       * @param string $description long localised info
5657       * @param array $defaultsetting ('value'=>string, '__construct'=>bool)
5658       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
5659       * @param int $size default field size
5660       */
5661      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
5662          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $paramtype, $size);
5663          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5664      }
5665  }
5666  
5667  
5668  /**
5669   * Checkbox with an advanced checkbox that controls an additional $name.'_adv' config setting.
5670   *
5671   * @copyright 2009 Petr Skoda (http://skodak.org)
5672   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5673   */
5674  class admin_setting_configcheckbox_with_advanced extends admin_setting_configcheckbox {
5675  
5676      /**
5677       * Constructor
5678       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5679       * @param string $visiblename localised
5680       * @param string $description long localised info
5681       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
5682       * @param string $yes value used when checked
5683       * @param string $no value used when not checked
5684       */
5685      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5686          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5687          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5688      }
5689  
5690  }
5691  
5692  
5693  /**
5694   * Checkbox with an advanced checkbox that controls an additional $name.'_locked' config setting.
5695   *
5696   * This is nearly a copy/paste of admin_setting_configcheckbox_with_adv
5697   *
5698   * @copyright 2010 Sam Hemelryk
5699   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5700   */
5701  class admin_setting_configcheckbox_with_lock extends admin_setting_configcheckbox {
5702      /**
5703       * Constructor
5704       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5705       * @param string $visiblename localised
5706       * @param string $description long localised info
5707       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5708       * @param string $yes value used when checked
5709       * @param string $no value used when not checked
5710       */
5711      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5712          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5713          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5714      }
5715  
5716  }
5717  
5718  /**
5719   * Autocomplete as you type form element.
5720   *
5721   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5722   */
5723  class admin_setting_configselect_autocomplete extends admin_setting_configselect {
5724      /** @var boolean $tags Should we allow typing new entries to the field? */
5725      protected $tags = false;
5726      /** @var string $ajax Name of an AMD module to send/process ajax requests. */
5727      protected $ajax = '';
5728      /** @var string $placeholder Placeholder text for an empty list. */
5729      protected $placeholder = '';
5730      /** @var bool $casesensitive Whether the search has to be case-sensitive. */
5731      protected $casesensitive = false;
5732      /** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
5733      protected $showsuggestions = true;
5734      /** @var string $noselectionstring String that is shown when there are no selections. */
5735      protected $noselectionstring = '';
5736  
5737      /**
5738       * Returns XHTML select field and wrapping div(s)
5739       *
5740       * @see output_select_html()
5741       *
5742       * @param string $data the option to show as selected
5743       * @param string $query
5744       * @return string XHTML field and wrapping div
5745       */
5746      public function output_html($data, $query='') {
5747          global $PAGE;
5748  
5749          $html = parent::output_html($data, $query);
5750  
5751          if ($html === '') {
5752              return $html;
5753          }
5754  
5755          $this->placeholder = get_string('search');
5756  
5757          $params = array('#' . $this->get_id(), $this->tags, $this->ajax,
5758              $this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring);
5759  
5760          // Load autocomplete wrapper for select2 library.
5761          $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params);
5762  
5763          return $html;
5764      }
5765  }
5766  
5767  /**
5768   * Dropdown menu with an advanced checkbox, that controls a additional $name.'_adv' setting.
5769   *
5770   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5771   */
5772  class admin_setting_configselect_with_advanced extends admin_setting_configselect {
5773      /**
5774       * Calls parent::__construct with specific arguments
5775       */
5776      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5777          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5778          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5779      }
5780  
5781  }
5782  
5783  /**
5784   * Select with an advanced checkbox that controls an additional $name.'_locked' config setting.
5785   *
5786   * @copyright 2017 Marina Glancy
5787   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5788   */
5789  class admin_setting_configselect_with_lock extends admin_setting_configselect {
5790      /**
5791       * Constructor
5792       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
5793       *     or 'myplugin/mysetting' for ones in config_plugins.
5794       * @param string $visiblename localised
5795       * @param string $description long localised info
5796       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5797       * @param array $choices array of $value=>$label for each selection
5798       */
5799      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5800          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5801          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5802      }
5803  }
5804  
5805  
5806  /**
5807   * Graded roles in gradebook
5808   *
5809   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5810   */
5811  class admin_setting_special_gradebookroles extends admin_setting_pickroles {
5812      /**
5813       * Calls parent::__construct with specific arguments
5814       */
5815      public function __construct() {
5816          parent::__construct('gradebookroles', get_string('gradebookroles', 'admin'),
5817              get_string('configgradebookroles', 'admin'),
5818              array('student'));
5819      }
5820  }
5821  
5822  
5823  /**
5824   *
5825   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5826   */
5827  class admin_setting_regradingcheckbox extends admin_setting_configcheckbox {
5828      /**
5829       * Saves the new settings passed in $data
5830       *
5831       * @param string $data
5832       * @return mixed string or Array
5833       */
5834      public function write_setting($data) {
5835          global $CFG, $DB;
5836  
5837          $oldvalue  = $this->config_read($this->name);
5838          $return    = parent::write_setting($data);
5839          $newvalue  = $this->config_read($this->name);
5840  
5841          if ($oldvalue !== $newvalue) {
5842          // force full regrading
5843              $DB->set_field('grade_items', 'needsupdate', 1, array('needsupdate'=>0));
5844          }
5845  
5846          return $return;
5847      }
5848  }
5849  
5850  
5851  /**
5852   * Which roles to show on course description page
5853   *
5854   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5855   */
5856  class admin_setting_special_coursecontact extends admin_setting_pickroles {
5857      /**
5858       * Calls parent::__construct with specific arguments
5859       */
5860      public function __construct() {
5861          parent::__construct('coursecontact', get_string('coursecontact', 'admin'),
5862              get_string('coursecontact_desc', 'admin'),
5863              array('editingteacher'));
5864          $this->set_updatedcallback(function (){
5865              cache::make('core', 'coursecontacts')->purge();
5866          });
5867      }
5868  }
5869  
5870  
5871  /**
5872   *
5873   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5874   */
5875  class admin_setting_special_gradelimiting extends admin_setting_configcheckbox {
5876      /**
5877       * Calls parent::__construct with specific arguments
5878       */
5879      public function __construct() {
5880          parent::__construct('unlimitedgrades', get_string('unlimitedgrades', 'grades'),
5881              get_string('unlimitedgrades_help', 'grades'), '0', '1', '0');
5882      }
5883  
5884      /**
5885       * Old syntax of class constructor. Deprecated in PHP7.
5886       *
5887       * @deprecated since Moodle 3.1
5888       */
5889      public function admin_setting_special_gradelimiting() {
5890          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
5891          self::__construct();
5892      }
5893  
5894      /**
5895       * Force site regrading
5896       */
5897      function regrade_all() {
5898          global $CFG;
5899          require_once("$CFG->libdir/gradelib.php");
5900          grade_force_site_regrading();
5901      }
5902  
5903      /**
5904       * Saves the new settings
5905       *
5906       * @param mixed $data
5907       * @return string empty string or error message
5908       */
5909      function write_setting($data) {
5910          $previous = $this->get_setting();
5911  
5912          if ($previous === null) {
5913              if ($data) {
5914                  $this->regrade_all();
5915              }
5916          } else {
5917              if ($data != $previous) {
5918                  $this->regrade_all();
5919              }
5920          }
5921          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
5922      }
5923  
5924  }
5925  
5926  /**
5927   * Special setting for $CFG->grade_minmaxtouse.
5928   *
5929   * @package    core
5930   * @copyright  2015 Frédéric Massart - FMCorz.net
5931   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5932   */
5933  class admin_setting_special_grademinmaxtouse extends admin_setting_configselect {
5934  
5935      /**
5936       * Constructor.
5937       */
5938      public function __construct() {
5939          parent::__construct('grade_minmaxtouse', new lang_string('minmaxtouse', 'grades'),
5940              new lang_string('minmaxtouse_desc', 'grades'), GRADE_MIN_MAX_FROM_GRADE_ITEM,
5941              array(
5942                  GRADE_MIN_MAX_FROM_GRADE_ITEM => get_string('gradeitemminmax', 'grades'),
5943                  GRADE_MIN_MAX_FROM_GRADE_GRADE => get_string('gradegrademinmax', 'grades')
5944              )
5945          );
5946      }
5947  
5948      /**
5949       * Saves the new setting.
5950       *
5951       * @param mixed $data
5952       * @return string empty string or error message
5953       */
5954      function write_setting($data) {
5955          global $CFG;
5956  
5957          $previous = $this->get_setting();
5958          $result = parent::write_setting($data);
5959  
5960          // If saved and the value has changed.
5961          if (empty($result) && $previous != $data) {
5962              require_once($CFG->libdir . '/gradelib.php');
5963              grade_force_site_regrading();
5964          }
5965  
5966          return $result;
5967      }
5968  
5969  }
5970  
5971  
5972  /**
5973   * Primary grade export plugin - has state tracking.
5974   *
5975   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5976   */
5977  class admin_setting_special_gradeexport extends admin_setting_configmulticheckbox {
5978      /**
5979       * Calls parent::__construct with specific arguments
5980       */
5981      public function __construct() {
5982          parent::__construct('gradeexport', get_string('gradeexport', 'admin'),
5983              get_string('configgradeexport', 'admin'), array(), NULL);
5984      }
5985  
5986      /**
5987       * Load the available choices for the multicheckbox
5988       *
5989       * @return bool always returns true
5990       */
5991      public function load_choices() {
5992          if (is_array($this->choices)) {
5993              return true;
5994          }
5995          $this->choices = array();
5996  
5997          if ($plugins = core_component::get_plugin_list('gradeexport')) {
5998              foreach($plugins as $plugin => $unused) {
5999                  $this->choices[$plugin] = get_string('pluginname', 'gradeexport_'.$plugin);
6000              }
6001          }
6002          return true;
6003      }
6004  }
6005  
6006  
6007  /**
6008   * A setting for setting the default grade point value. Must be an integer between 1 and $CFG->gradepointmax.
6009   *
6010   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6011   */
6012  class admin_setting_special_gradepointdefault extends admin_setting_configtext {
6013      /**
6014       * Config gradepointmax constructor
6015       *
6016       * @param string $name Overidden by "gradepointmax"
6017       * @param string $visiblename Overridden by "gradepointmax" language string.
6018       * @param string $description Overridden by "gradepointmax_help" language string.
6019       * @param string $defaultsetting Not used, overridden by 100.
6020       * @param mixed $paramtype Overridden by PARAM_INT.
6021       * @param int $size Overridden by 5.
6022       */
6023      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
6024          $name = 'gradepointdefault';
6025          $visiblename = get_string('gradepointdefault', 'grades');
6026          $description = get_string('gradepointdefault_help', 'grades');
6027          $defaultsetting = 100;
6028          $paramtype = PARAM_INT;
6029          $size = 5;
6030          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
6031      }
6032  
6033      /**
6034       * Validate data before storage
6035       * @param string $data The submitted data
6036       * @return bool|string true if ok, string if error found
6037       */
6038      public function validate($data) {
6039          global $CFG;
6040          if (((string)(int)$data === (string)$data && $data > 0 && $data <= $CFG->gradepointmax)) {
6041              return true;
6042          } else {
6043              return get_string('gradepointdefault_validateerror', 'grades');
6044          }
6045      }
6046  }
6047  
6048  
6049  /**
6050   * A setting for setting the maximum grade value. Must be an integer between 1 and 10000.
6051   *
6052   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6053   */
6054  class admin_setting_special_gradepointmax extends admin_setting_configtext {
6055  
6056      /**
6057       * Config gradepointmax constructor
6058       *
6059       * @param string $name Overidden by "gradepointmax"
6060       * @param string $visiblename Overridden by "gradepointmax" language string.
6061       * @param string $description Overridden by "gradepointmax_help" language string.
6062       * @param string $defaultsetting Not used, overridden by 100.
6063       * @param mixed $paramtype Overridden by PARAM_INT.
6064       * @param int $size Overridden by 5.
6065       */
6066      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
6067          $name = 'gradepointmax';
6068          $visiblename = get_string('gradepointmax', 'grades');
6069          $description = get_string('gradepointmax_help', 'grades');
6070          $defaultsetting = 100;
6071          $paramtype = PARAM_INT;
6072          $size = 5;
6073          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
6074      }
6075  
6076      /**
6077       * Save the selected setting
6078       *
6079       * @param string $data The selected site
6080       * @return string empty string or error message
6081       */
6082      public function write_setting($data) {
6083          if ($data === '') {
6084              $data = (int)$this->defaultsetting;
6085          } else {
6086              $data = $data;
6087          }
6088          return parent::write_setting($data);
6089      }
6090  
6091      /**
6092       * Validate data before storage
6093       * @param string $data The submitted data
6094       * @return bool|string true if ok, string if error found
6095       */
6096      public function validate($data) {
6097          if (((string)(int)$data === (string)$data && $data > 0 && $data <= 10000)) {
6098              return true;
6099          } else {
6100              return get_string('gradepointmax_validateerror', 'grades');
6101          }
6102      }
6103  
6104      /**
6105       * Return an XHTML string for the setting
6106       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6107       * @param string $query search query to be highlighted
6108       * @return string XHTML to display control
6109       */
6110      public function output_html($data, $query = '') {
6111          global $OUTPUT;
6112  
6113          $default = $this->get_defaultsetting();
6114          $context = (object) [
6115              'size' => $this->size,
6116              'id' => $this->get_id(),
6117              'name' => $this->get_full_name(),
6118              'value' => $data,
6119              'attributes' => [
6120                  'maxlength' => 5
6121              ],
6122              'forceltr' => $this->get_force_ltr()
6123          ];
6124          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
6125  
6126          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
6127      }
6128  }
6129  
6130  
6131  /**
6132   * Grade category settings
6133   *
6134   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6135   */
6136  class admin_setting_gradecat_combo extends admin_setting {
6137      /** @var array Array of choices */
6138      public $choices;
6139  
6140      /**
6141       * Sets choices and calls parent::__construct with passed arguments
6142       * @param string $name
6143       * @param string $visiblename
6144       * @param string $description
6145       * @param mixed $defaultsetting string or array depending on implementation
6146       * @param array $choices An array of choices for the control
6147       */
6148      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
6149          $this->choices = $choices;
6150          parent::__construct($name, $visiblename, $description, $defaultsetting);
6151      }
6152  
6153      /**
6154       * Return the current setting(s) array
6155       *
6156       * @return array Array of value=>xx, forced=>xx, adv=>xx
6157       */
6158      public function get_setting() {
6159          global $CFG;
6160  
6161          $value = $this->config_read($this->name);
6162          $flag  = $this->config_read($this->name.'_flag');
6163  
6164          if (is_null($value) or is_null($flag)) {
6165              return NULL;
6166          }
6167  
6168          $flag   = (int)$flag;
6169          $forced = (boolean)(1 & $flag); // first bit
6170          $adv    = (boolean)(2 & $flag); // second bit
6171  
6172          return array('value' => $value, 'forced' => $forced, 'adv' => $adv);
6173      }
6174  
6175      /**
6176       * Save the new settings passed in $data
6177       *
6178       * @todo Add vartype handling to ensure $data is array
6179       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6180       * @return string empty or error message
6181       */
6182      public function write_setting($data) {
6183          global $CFG;
6184  
6185          $value  = $data['value'];
6186          $forced = empty($data['forced']) ? 0 : 1;
6187          $adv    = empty($data['adv'])    ? 0 : 2;
6188          $flag   = ($forced | $adv); //bitwise or
6189  
6190          if (!in_array($value, array_keys($this->choices))) {
6191              return 'Error setting ';
6192          }
6193  
6194          $oldvalue  = $this->config_read($this->name);
6195          $oldflag   = (int)$this->config_read($this->name.'_flag');
6196          $oldforced = (1 & $oldflag); // first bit
6197  
6198          $result1 = $this->config_write($this->name, $value);
6199          $result2 = $this->config_write($this->name.'_flag', $flag);
6200  
6201          // force regrade if needed
6202          if ($oldforced != $forced or ($forced and $value != $oldvalue)) {
6203              require_once($CFG->libdir.'/gradelib.php');
6204              grade_category::updated_forced_settings();
6205          }
6206  
6207          if ($result1 and $result2) {
6208              return '';
6209          } else {
6210              return get_string('errorsetting', 'admin');
6211          }
6212      }
6213  
6214      /**
6215       * Return XHTML to display the field and wrapping div
6216       *
6217       * @todo Add vartype handling to ensure $data is array
6218       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6219       * @param string $query
6220       * @return string XHTML to display control
6221       */
6222      public function output_html($data, $query='') {
6223          global $OUTPUT;
6224  
6225          $value  = $data['value'];
6226  
6227          $default = $this->get_defaultsetting();
6228          if (!is_null($default)) {
6229              $defaultinfo = array();
6230              if (isset($this->choices[$default['value']])) {
6231                  $defaultinfo[] = $this->choices[$default['value']];
6232              }
6233              if (!empty($default['forced'])) {
6234                  $defaultinfo[] = get_string('force');
6235              }
6236              if (!empty($default['adv'])) {
6237                  $defaultinfo[] = get_string('advanced');
6238              }
6239              $defaultinfo = implode(', ', $defaultinfo);
6240  
6241          } else {
6242              $defaultinfo = NULL;
6243          }
6244  
6245          $options = $this->choices;
6246          $context = (object) [
6247              'id' => $this->get_id(),
6248              'name' => $this->get_full_name(),
6249              'forced' => !empty($data['forced']),
6250              'advanced' => !empty($data['adv']),
6251              'options' => array_map(function($option) use ($options, $value) {
6252                  return [
6253                      'value' => $option,
6254                      'name' => $options[$option],
6255                      'selected' => $option == $value
6256                  ];
6257              }, array_keys($options)),
6258          ];
6259  
6260          $element = $OUTPUT->render_from_template('core_admin/setting_gradecat_combo', $context);
6261  
6262          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
6263      }
6264  }
6265  
6266  
6267  /**
6268   * Selection of grade report in user profiles
6269   *
6270   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6271   */
6272  class admin_setting_grade_profilereport extends admin_setting_configselect {
6273      /**
6274       * Calls parent::__construct with specific arguments
6275       */
6276      public function __construct() {
6277          parent::__construct('grade_profilereport', get_string('profilereport', 'grades'), get_string('profilereport_help', 'grades'), 'user', null);
6278      }
6279  
6280      /**
6281       * Loads an array of choices for the configselect control
6282       *
6283       * @return bool always return true
6284       */
6285      public function load_choices() {
6286          if (is_array($this->choices)) {
6287              return true;
6288          }
6289          $this->choices = array();
6290  
6291          global $CFG;
6292          require_once($CFG->libdir.'/gradelib.php');
6293  
6294          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6295              if (file_exists($plugindir.'/lib.php')) {
6296                  require_once($plugindir.'/lib.php');
6297                  $functionname = 'grade_report_'.$plugin.'_profilereport';
6298                  if (function_exists($functionname)) {
6299                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_'.$plugin);
6300                  }
6301              }
6302          }
6303          return true;
6304      }
6305  }
6306  
6307  /**
6308   * Provides a selection of grade reports to be used for "grades".
6309   *
6310   * @copyright 2015 Adrian Greeve <adrian@moodle.com>
6311   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6312   */
6313  class admin_setting_my_grades_report extends admin_setting_configselect {
6314  
6315      /**
6316       * Calls parent::__construct with specific arguments.
6317       */
6318      public function __construct() {
6319          parent::__construct('grade_mygrades_report', new lang_string('mygrades', 'grades'),
6320                  new lang_string('mygrades_desc', 'grades'), 'overview', null);
6321      }
6322  
6323      /**
6324       * Loads an array of choices for the configselect control.
6325       *
6326       * @return bool always returns true.
6327       */
6328      public function load_choices() {
6329          global $CFG; // Remove this line and behold the horror of behat test failures!
6330          $this->choices = array();
6331          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6332              if (file_exists($plugindir . '/lib.php')) {
6333                  require_once($plugindir . '/lib.php');
6334                  // Check to see if the class exists. Check the correct plugin convention first.
6335                  if (class_exists('gradereport_' . $plugin)) {
6336                      $classname = 'gradereport_' . $plugin;
6337                  } else if (class_exists('grade_report_' . $plugin)) {
6338                      // We are using the old plugin naming convention.
6339                      $classname = 'grade_report_' . $plugin;
6340                  } else {
6341                      continue;
6342                  }
6343                  if ($classname::supports_mygrades()) {
6344                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_' . $plugin);
6345                  }
6346              }
6347          }
6348          // Add an option to specify an external url.
6349          $this->choices['external'] = get_string('externalurl', 'grades');
6350          return true;
6351      }
6352  }
6353  
6354  /**
6355   * Special class for register auth selection
6356   *
6357   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6358   */
6359  class admin_setting_special_registerauth extends admin_setting_configselect {
6360      /**
6361       * Calls parent::__construct with specific arguments
6362       */
6363      public function __construct() {
6364          parent::__construct('registerauth', get_string('selfregistration', 'auth'), get_string('selfregistration_help', 'auth'), '', null);
6365      }
6366  
6367      /**
6368       * Returns the default option
6369       *
6370       * @return string empty or default option
6371       */
6372      public function get_defaultsetting() {
6373          $this->load_choices();
6374          $defaultsetting = parent::get_defaultsetting();
6375          if (array_key_exists($defaultsetting, $this->choices)) {
6376              return $defaultsetting;
6377          } else {
6378              return '';
6379          }
6380      }
6381  
6382      /**
6383       * Loads the possible choices for the array
6384       *
6385       * @return bool always returns true
6386       */
6387      public function load_choices() {
6388          global $CFG;
6389  
6390          if (is_array($this->choices)) {
6391              return true;
6392          }
6393          $this->choices = array();
6394          $this->choices[''] = get_string('disable');
6395  
6396          $authsenabled = get_enabled_auth_plugins();
6397  
6398          foreach ($authsenabled as $auth) {
6399              $authplugin = get_auth_plugin($auth);
6400              if (!$authplugin->can_signup()) {
6401                  continue;
6402              }
6403              // Get the auth title (from core or own auth lang files)
6404              $authtitle = $authplugin->get_title();
6405              $this->choices[$auth] = $authtitle;
6406          }
6407          return true;
6408      }
6409  }
6410  
6411  
6412  /**
6413   * General plugins manager
6414   */
6415  class admin_page_pluginsoverview extends admin_externalpage {
6416  
6417      /**
6418       * Sets basic information about the external page
6419       */
6420      public function __construct() {
6421          global $CFG;
6422          parent::__construct('pluginsoverview', get_string('pluginsoverview', 'core_admin'),
6423              "$CFG->wwwroot/$CFG->admin/plugins.php");
6424      }
6425  }
6426  
6427  /**
6428   * Module manage page
6429   *
6430   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6431   */
6432  class admin_page_managemods extends admin_externalpage {
6433      /**
6434       * Calls parent::__construct with specific arguments
6435       */
6436      public function __construct() {
6437          global $CFG;
6438          parent::__construct('managemodules', get_string('modsettings', 'admin'), "$CFG->wwwroot/$CFG->admin/modules.php");
6439      }
6440  
6441      /**
6442       * Try to find the specified module
6443       *
6444       * @param string $query The module to search for
6445       * @return array
6446       */
6447      public function search($query) {
6448          global $CFG, $DB;
6449          if ($result = parent::search($query)) {
6450              return $result;
6451          }
6452  
6453          $found = false;
6454          if ($modules = $DB->get_records('modules')) {
6455              foreach ($modules as $module) {
6456                  if (!file_exists("$CFG->dirroot/mod/$module->name/lib.php")) {
6457                      continue;
6458                  }
6459                  if (strpos($module->name, $query) !== false) {
6460                      $found = true;
6461                      break;
6462                  }
6463                  $strmodulename = get_string('modulename', $module->name);
6464                  if (strpos(core_text::strtolower($strmodulename), $query) !== false) {
6465                      $found = true;
6466                      break;
6467                  }
6468              }
6469          }
6470          if ($found) {
6471              $result = new stdClass();
6472              $result->page     = $this;
6473              $result->settings = array();
6474              return array($this->name => $result);
6475          } else {
6476              return array();
6477          }
6478      }
6479  }
6480  
6481  
6482  /**
6483   * Special class for enrol plugins management.
6484   *
6485   * @copyright 2010 Petr Skoda {@link http://skodak.org}
6486   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6487   */
6488  class admin_setting_manageenrols extends admin_setting {
6489      /**
6490       * Calls parent::__construct with specific arguments
6491       */
6492      public function __construct() {
6493          $this->nosave = true;
6494          parent::__construct('enrolsui', get_string('manageenrols', 'enrol'), '', '');
6495      }
6496  
6497      /**
6498       * Always returns true, does nothing
6499       *
6500       * @return true
6501       */
6502      public function get_setting() {
6503          return true;
6504      }
6505  
6506      /**
6507       * Always returns true, does nothing
6508       *
6509       * @return true
6510       */
6511      public function get_defaultsetting() {
6512          return true;
6513      }
6514  
6515      /**
6516       * Always returns '', does not write anything
6517       *
6518       * @return string Always returns ''
6519       */
6520      public function write_setting($data) {
6521      // do not write any setting
6522          return '';
6523      }
6524  
6525      /**
6526       * Checks if $query is one of the available enrol plugins
6527       *
6528       * @param string $query The string to search for
6529       * @return bool Returns true if found, false if not
6530       */
6531      public function is_related($query) {
6532          if (parent::is_related($query)) {
6533              return true;
6534          }
6535  
6536          $query = core_text::strtolower($query);
6537          $enrols = enrol_get_plugins(false);
6538          foreach ($enrols as $name=>$enrol) {
6539              $localised = get_string('pluginname', 'enrol_'.$name);
6540              if (strpos(core_text::strtolower($name), $query) !== false) {
6541                  return true;
6542              }
6543              if (strpos(core_text::strtolower($localised), $query) !== false) {
6544                  return true;
6545              }
6546          }
6547          return false;
6548      }
6549  
6550      /**
6551       * Builds the XHTML to display the control
6552       *
6553       * @param string $data Unused
6554       * @param string $query
6555       * @return string
6556       */
6557      public function output_html($data, $query='') {
6558          global $CFG, $OUTPUT, $DB, $PAGE;
6559  
6560          // Display strings.
6561          $strup        = get_string('up');
6562          $strdown      = get_string('down');
6563          $strsettings  = get_string('settings');
6564          $strenable    = get_string('enable');
6565          $strdisable   = get_string('disable');
6566          $struninstall = get_string('uninstallplugin', 'core_admin');
6567          $strusage     = get_string('enrolusage', 'enrol');
6568          $strversion   = get_string('version');
6569          $strtest      = get_string('testsettings', 'core_enrol');
6570  
6571          $pluginmanager = core_plugin_manager::instance();
6572  
6573          $enrols_available = enrol_get_plugins(false);
6574          $active_enrols    = enrol_get_plugins(true);
6575  
6576          $allenrols = array();
6577          foreach ($active_enrols as $key=>$enrol) {
6578              $allenrols[$key] = true;
6579          }
6580          foreach ($enrols_available as $key=>$enrol) {
6581              $allenrols[$key] = true;
6582          }
6583          // Now find all borked plugins and at least allow then to uninstall.
6584          $condidates = $DB->get_fieldset_sql("SELECT DISTINCT enrol FROM {enrol}");
6585          foreach ($condidates as $candidate) {
6586              if (empty($allenrols[$candidate])) {
6587                  $allenrols[$candidate] = true;
6588              }
6589          }
6590  
6591          $return = $OUTPUT->heading(get_string('actenrolshhdr', 'enrol'), 3, 'main', true);
6592          $return .= $OUTPUT->box_start('generalbox enrolsui');
6593  
6594          $table = new html_table();
6595          $table->head  = array(get_string('name'), $strusage, $strversion, $strenable, $strup.'/'.$strdown, $strsettings, $strtest, $struninstall);
6596          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
6597          $table->id = 'courseenrolmentplugins';
6598          $table->attributes['class'] = 'admintable generaltable';
6599          $table->data  = array();
6600  
6601          // Iterate through enrol plugins and add to the display table.
6602          $updowncount = 1;
6603          $enrolcount = count($active_enrols);
6604          $url = new moodle_url('/admin/enrol.php', array('sesskey'=>sesskey()));
6605          $printed = array();
6606          foreach($allenrols as $enrol => $unused) {
6607              $plugininfo = $pluginmanager->get_plugin_info('enrol_'.$enrol);
6608              $version = get_config('enrol_'.$enrol, 'version');
6609              if ($version === false) {
6610                  $version = '';
6611              }
6612  
6613              if (get_string_manager()->string_exists('pluginname', 'enrol_'.$enrol)) {
6614                  $name = get_string('pluginname', 'enrol_'.$enrol);
6615              } else {
6616                  $name = $enrol;
6617              }
6618              // Usage.
6619              $ci = $DB->count_records('enrol', array('enrol'=>$enrol));
6620              $cp = $DB->count_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($enrol));
6621              $usage = "$ci / $cp";
6622  
6623              // Hide/show links.
6624              $class = '';
6625              if (isset($active_enrols[$enrol])) {
6626                  $aurl = new moodle_url($url, array('action'=>'disable', 'enrol'=>$enrol));
6627                  $hideshow = "<a href=\"$aurl\">";
6628                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
6629                  $enabled = true;
6630                  $displayname = $name;
6631              } else if (isset($enrols_available[$enrol])) {
6632                  $aurl = new moodle_url($url, array('action'=>'enable', 'enrol'=>$enrol));
6633                  $hideshow = "<a href=\"$aurl\">";
6634                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
6635                  $enabled = false;
6636                  $displayname = $name;
6637                  $class = 'dimmed_text';
6638              } else {
6639                  $hideshow = '';
6640                  $enabled = false;
6641                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
6642              }
6643              if ($PAGE->theme->resolve_image_location('icon', 'enrol_' . $name, false)) {
6644                  $icon = $OUTPUT->pix_icon('icon', '', 'enrol_' . $name, array('class' => 'icon pluginicon'));
6645              } else {
6646                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
6647              }
6648  
6649              // Up/down link (only if enrol is enabled).
6650              $updown = '';
6651              if ($enabled) {
6652                  if ($updowncount > 1) {
6653                      $aurl = new moodle_url($url, array('action'=>'up', 'enrol'=>$enrol));
6654                      $updown .= "<a href=\"$aurl\">";
6655                      $updown .= $OUTPUT->pix_icon('t/up', $strup) . '</a>&nbsp;';
6656                  } else {
6657                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6658                  }
6659                  if ($updowncount < $enrolcount) {
6660                      $aurl = new moodle_url($url, array('action'=>'down', 'enrol'=>$enrol));
6661                      $updown .= "<a href=\"$aurl\">";
6662                      $updown .= $OUTPUT->pix_icon('t/down', $strdown) . '</a>&nbsp;';
6663                  } else {
6664                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6665                  }
6666                  ++$updowncount;
6667              }
6668  
6669              // Add settings link.
6670              if (!$version) {
6671                  $settings = '';
6672              } else if ($surl = $plugininfo->get_settings_url()) {
6673                  $settings = html_writer::link($surl, $strsettings);
6674              } else {
6675                  $settings = '';
6676              }
6677  
6678              // Add uninstall info.
6679              $uninstall = '';
6680              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol, 'manage')) {
6681                  $uninstall = html_writer::link($uninstallurl, $struninstall);
6682              }
6683  
6684              $test = '';
6685              if (!empty($enrols_available[$enrol]) and method_exists($enrols_available[$enrol], 'test_settings')) {
6686                  $testsettingsurl = new moodle_url('/enrol/test_settings.php', array('enrol'=>$enrol, 'sesskey'=>sesskey()));
6687                  $test = html_writer::link($testsettingsurl, $strtest);
6688              }
6689  
6690              // Add a row to the table.
6691              $row = new html_table_row(array($icon.$displayname, $usage, $version, $hideshow, $updown, $settings, $test, $uninstall));
6692              if ($class) {
6693                  $row->attributes['class'] = $class;
6694              }
6695              $table->data[] = $row;
6696  
6697              $printed[$enrol] = true;
6698          }
6699  
6700          $return .= html_writer::table($table);
6701          $return .= get_string('configenrolplugins', 'enrol').'<br />'.get_string('tablenosave', 'admin');
6702          $return .= $OUTPUT->box_end();
6703          return highlight($query, $return);
6704      }
6705  }
6706  
6707  
6708  /**
6709   * Blocks manage page
6710   *
6711   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6712   */
6713  class admin_page_manageblocks extends admin_externalpage {
6714      /**
6715       * Calls parent::__construct with specific arguments
6716       */
6717      public function __construct() {
6718          global $CFG;
6719          parent::__construct('manageblocks', get_string('blocksettings', 'admin'), "$CFG->wwwroot/$CFG->admin/blocks.php");
6720      }
6721  
6722      /**
6723       * Search for a specific block
6724       *
6725       * @param string $query The string to search for
6726       * @return array
6727       */
6728      public function search($query) {
6729          global $CFG, $DB;
6730          if ($result = parent::search($query)) {
6731              return $result;
6732          }
6733  
6734          $found = false;
6735          if ($blocks = $DB->get_records('block')) {
6736              foreach ($blocks as $block) {
6737                  if (!file_exists("$CFG->dirroot/blocks/$block->name/")) {
6738                      continue;
6739                  }
6740                  if (strpos($block->name, $query) !== false) {
6741                      $found = true;
6742                      break;
6743                  }
6744                  $strblockname = get_string('pluginname', 'block_'.$block->name);
6745                  if (strpos(core_text::strtolower($strblockname), $query) !== false) {
6746                      $found = true;
6747                      break;
6748                  }
6749              }
6750          }
6751          if ($found) {
6752              $result = new stdClass();
6753              $result->page     = $this;
6754              $result->settings = array();
6755              return array($this->name => $result);
6756          } else {
6757              return array();
6758          }
6759      }
6760  }
6761  
6762  /**
6763   * Message outputs configuration
6764   *
6765   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6766   */
6767  class admin_page_managemessageoutputs extends admin_externalpage {
6768      /**
6769       * Calls parent::__construct with specific arguments
6770       */
6771      public function __construct() {
6772          global $CFG;
6773          parent::__construct('managemessageoutputs',
6774              get_string('defaultmessageoutputs', 'message'),
6775              new moodle_url('/admin/message.php')
6776          );
6777      }
6778  
6779      /**
6780       * Search for a specific message processor
6781       *
6782       * @param string $query The string to search for
6783       * @return array
6784       */
6785      public function search($query) {
6786          global $CFG, $DB;
6787          if ($result = parent::search($query)) {
6788              return $result;
6789          }
6790  
6791          $found = false;
6792          if ($processors = get_message_processors()) {
6793              foreach ($processors as $processor) {
6794                  if (!$processor->available) {
6795                      continue;
6796                  }
6797                  if (strpos($processor->name, $query) !== false) {
6798                      $found = true;
6799                      break;
6800                  }
6801                  $strprocessorname = get_string('pluginname', 'message_'.$processor->name);
6802                  if (strpos(core_text::strtolower($strprocessorname), $query) !== false) {
6803                      $found = true;
6804                      break;
6805                  }
6806              }
6807          }
6808          if ($found) {
6809              $result = new stdClass();
6810              $result->page     = $this;
6811              $result->settings = array();
6812              return array($this->name => $result);
6813          } else {
6814              return array();
6815          }
6816      }
6817  }
6818  
6819  /**
6820   * Manage question behaviours page
6821   *
6822   * @copyright  2011 The Open University
6823   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6824   */
6825  class admin_page_manageqbehaviours extends admin_externalpage {
6826      /**
6827       * Constructor
6828       */
6829      public function __construct() {
6830          global $CFG;
6831          parent::__construct('manageqbehaviours', get_string('manageqbehaviours', 'admin'),
6832                  new moodle_url('/admin/qbehaviours.php'));
6833      }
6834  
6835      /**
6836       * Search question behaviours for the specified string
6837       *
6838       * @param string $query The string to search for in question behaviours
6839       * @return array
6840       */
6841      public function search($query) {
6842          global $CFG;
6843          if ($result = parent::search($query)) {
6844              return $result;
6845          }
6846  
6847          $found = false;
6848          require_once($CFG->dirroot . '/question/engine/lib.php');
6849          foreach (core_component::get_plugin_list('qbehaviour') as $behaviour => $notused) {
6850              if (strpos(core_text::strtolower(question_engine::get_behaviour_name($behaviour)),
6851                      $query) !== false) {
6852                  $found = true;
6853                  break;
6854              }
6855          }
6856          if ($found) {
6857              $result = new stdClass();
6858              $result->page     = $this;
6859              $result->settings = array();
6860              return array($this->name => $result);
6861          } else {
6862              return array();
6863          }
6864      }
6865  }
6866  
6867  
6868  /**
6869   * Question type manage page
6870   *
6871   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6872   */
6873  class admin_page_manageqtypes extends admin_externalpage {
6874      /**
6875       * Calls parent::__construct with specific arguments
6876       */
6877      public function __construct() {
6878          global $CFG;
6879          parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'),
6880                  new moodle_url('/admin/qtypes.php'));
6881      }
6882  
6883      /**
6884       * Search question types for the specified string
6885       *
6886       * @param string $query The string to search for in question types
6887       * @return array
6888       */
6889      public function search($query) {
6890          global $CFG;
6891          if ($result = parent::search($query)) {
6892              return $result;
6893          }
6894  
6895          $found = false;
6896          require_once($CFG->dirroot . '/question/engine/bank.php');
6897          foreach (question_bank::get_all_qtypes() as $qtype) {
6898              if (strpos(core_text::strtolower($qtype->local_name()), $query) !== false) {
6899                  $found = true;
6900                  break;
6901              }
6902          }
6903          if ($found) {
6904              $result = new stdClass();
6905              $result->page     = $this;
6906              $result->settings = array();
6907              return array($this->name => $result);
6908          } else {
6909              return array();
6910          }
6911      }
6912  }
6913  
6914  
6915  class admin_page_manageportfolios extends admin_externalpage {
6916      /**
6917       * Calls parent::__construct with specific arguments
6918       */
6919      public function __construct() {
6920          global $CFG;
6921          parent::__construct('manageportfolios', get_string('manageportfolios', 'portfolio'),
6922                  "$CFG->wwwroot/$CFG->admin/portfolio.php");
6923      }
6924  
6925      /**
6926       * Searches page for the specified string.
6927       * @param string $query The string to search for
6928       * @return bool True if it is found on this page
6929       */
6930      public function search($query) {
6931          global $CFG;
6932          if ($result = parent::search($query)) {
6933              return $result;
6934          }
6935  
6936          $found = false;
6937          $portfolios = core_component::get_plugin_list('portfolio');
6938          foreach ($portfolios as $p => $dir) {
6939              if (strpos($p, $query) !== false) {
6940                  $found = true;
6941                  break;
6942              }
6943          }
6944          if (!$found) {
6945              foreach (portfolio_instances(false, false) as $instance) {
6946                  $title = $instance->get('name');
6947                  if (strpos(core_text::strtolower($title), $query) !== false) {
6948                      $found = true;
6949                      break;
6950                  }
6951              }
6952          }
6953  
6954          if ($found) {
6955              $result = new stdClass();
6956              $result->page     = $this;
6957              $result->settings = array();
6958              return array($this->name => $result);
6959          } else {
6960              return array();
6961          }
6962      }
6963  }
6964  
6965  
6966  class admin_page_managerepositories extends admin_externalpage {
6967      /**
6968       * Calls parent::__construct with specific arguments
6969       */
6970      public function __construct() {
6971          global $CFG;
6972          parent::__construct('managerepositories', get_string('manage',
6973                  'repository'), "$CFG->wwwroot/$CFG->admin/repository.php");
6974      }
6975  
6976      /**
6977       * Searches page for the specified string.
6978       * @param string $query The string to search for
6979       * @return bool True if it is found on this page
6980       */
6981      public function search($query) {
6982          global $CFG;
6983          if ($result = parent::search($query)) {
6984              return $result;
6985          }
6986  
6987          $found = false;
6988          $repositories= core_component::get_plugin_list('repository');
6989          foreach ($repositories as $p => $dir) {
6990              if (strpos($p, $query) !== false) {
6991                  $found = true;
6992                  break;
6993              }
6994          }
6995          if (!$found) {
6996              foreach (repository::get_types() as $instance) {
6997                  $title = $instance->get_typename();
6998                  if (strpos(core_text::strtolower($title), $query) !== false) {
6999                      $found = true;
7000                      break;
7001                  }
7002              }
7003          }
7004  
7005          if ($found) {
7006              $result = new stdClass();
7007              $result->page     = $this;
7008              $result->settings = array();
7009              return array($this->name => $result);
7010          } else {
7011              return array();
7012          }
7013      }
7014  }
7015  
7016  
7017  /**
7018   * Special class for authentication administration.
7019   *
7020   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7021   */
7022  class admin_setting_manageauths extends admin_setting {
7023      /**
7024       * Calls parent::__construct with specific arguments
7025       */
7026      public function __construct() {
7027          $this->nosave = true;
7028          parent::__construct('authsui', get_string('authsettings', 'admin'), '', '');
7029      }
7030  
7031      /**
7032       * Always returns true
7033       *
7034       * @return true
7035       */
7036      public function get_setting() {
7037          return true;
7038      }
7039  
7040      /**
7041       * Always returns true
7042       *
7043       * @return true
7044       */
7045      public function get_defaultsetting() {
7046          return true;
7047      }
7048  
7049      /**
7050       * Always returns '' and doesn't write anything
7051       *
7052       * @return string Always returns ''
7053       */
7054      public function write_setting($data) {
7055      // do not write any setting
7056          return '';
7057      }
7058  
7059      /**
7060       * Search to find if Query is related to auth plugin
7061       *
7062       * @param string $query The string to search for
7063       * @return bool true for related false for not
7064       */
7065      public function is_related($query) {
7066          if (parent::is_related($query)) {
7067              return true;
7068          }
7069  
7070          $authsavailable = core_component::get_plugin_list('auth');
7071          foreach ($authsavailable as $auth => $dir) {
7072              if (strpos($auth, $query) !== false) {
7073                  return true;
7074              }
7075              $authplugin = get_auth_plugin($auth);
7076              $authtitle = $authplugin->get_title();
7077              if (strpos(core_text::strtolower($authtitle), $query) !== false) {
7078                  return true;
7079              }
7080          }
7081          return false;
7082      }
7083  
7084      /**
7085       * Return XHTML to display control
7086       *
7087       * @param mixed $data Unused
7088       * @param string $query
7089       * @return string highlight
7090       */
7091      public function output_html($data, $query='') {
7092          global $CFG, $OUTPUT, $DB;
7093  
7094          // display strings
7095          $txt = get_strings(array('authenticationplugins', 'users', 'administration',
7096              'settings', 'edit', 'name', 'enable', 'disable',
7097              'up', 'down', 'none', 'users'));
7098          $txt->updown = "$txt->up/$txt->down";
7099          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7100          $txt->testsettings = get_string('testsettings', 'core_auth');
7101  
7102          $authsavailable = core_component::get_plugin_list('auth');
7103          get_enabled_auth_plugins(true); // fix the list of enabled auths
7104          if (empty($CFG->auth)) {
7105              $authsenabled = array();
7106          } else {
7107              $authsenabled = explode(',', $CFG->auth);
7108          }
7109  
7110          // construct the display array, with enabled auth plugins at the top, in order
7111          $displayauths = array();
7112          $registrationauths = array();
7113          $registrationauths[''] = $txt->disable;
7114          $authplugins = array();
7115          foreach ($authsenabled as $auth) {
7116              $authplugin = get_auth_plugin($auth);
7117              $authplugins[$auth] = $authplugin;
7118              /// Get the auth title (from core or own auth lang files)
7119              $authtitle = $authplugin->get_title();
7120              /// Apply titles
7121              $displayauths[$auth] = $authtitle;
7122              if ($authplugin->can_signup()) {
7123                  $registrationauths[$auth] = $authtitle;
7124              }
7125          }
7126  
7127          foreach ($authsavailable as $auth => $dir) {
7128              if (array_key_exists($auth, $displayauths)) {
7129                  continue; //already in the list
7130              }
7131              $authplugin = get_auth_plugin($auth);
7132              $authplugins[$auth] = $authplugin;
7133              /// Get the auth title (from core or own auth lang files)
7134              $authtitle = $authplugin->get_title();
7135              /// Apply titles
7136              $displayauths[$auth] = $authtitle;
7137              if ($authplugin->can_signup()) {
7138                  $registrationauths[$auth] = $authtitle;
7139              }
7140          }
7141  
7142          $return = $OUTPUT->heading(get_string('actauthhdr', 'auth'), 3, 'main');
7143          if (in_array('mnet', $authsenabled)) {
7144              $notify = new \core\output\notification(get_string('xmlrpcmnetauthenticationenabled', 'admin'),
7145                  \core\output\notification::NOTIFY_WARNING);
7146              $return .= $OUTPUT->render($notify);
7147          }
7148          $return .= $OUTPUT->box_start('generalbox authsui');
7149  
7150          $table = new html_table();
7151          $table->head  = array($txt->name, $txt->users, $txt->enable, $txt->updown, $txt->settings, $txt->testsettings, $txt->uninstall);
7152          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7153          $table->data  = array();
7154          $table->attributes['class'] = 'admintable generaltable';
7155          $table->id = 'manageauthtable';
7156  
7157          //add always enabled plugins first
7158          $displayname = $displayauths['manual'];
7159          $settings = "<a href=\"settings.php?section=authsettingmanual\">{$txt->settings}</a>";
7160          $usercount = $DB->count_records('user', array('auth'=>'manual', 'deleted'=>0));
7161          $table->data[] = array($displayname, $usercount, '', '', $settings, '', '');
7162          $displayname = $displayauths['nologin'];
7163          $usercount = $DB->count_records('user', array('auth'=>'nologin', 'deleted'=>0));
7164          $table->data[] = array($displayname, $usercount, '', '', '', '', '');
7165  
7166  
7167          // iterate through auth plugins and add to the display table
7168          $updowncount = 1;
7169          $authcount = count($authsenabled);
7170          $url = "auth.php?sesskey=" . sesskey();
7171          foreach ($displayauths as $auth => $name) {
7172              if ($auth == 'manual' or $auth == 'nologin') {
7173                  continue;
7174              }
7175              $class = '';
7176              // hide/show link
7177              if (in_array($auth, $authsenabled)) {
7178                  $hideshow = "<a href=\"$url&amp;action=disable&amp;auth=$auth\">";
7179                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7180                  $enabled = true;
7181                  $displayname = $name;
7182              }
7183              else {
7184                  $hideshow = "<a href=\"$url&amp;action=enable&amp;auth=$auth\">";
7185                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7186                  $enabled = false;
7187                  $displayname = $name;
7188                  $class = 'dimmed_text';
7189              }
7190  
7191              $usercount = $DB->count_records('user', array('auth'=>$auth, 'deleted'=>0));
7192  
7193              // up/down link (only if auth is enabled)
7194              $updown = '';
7195              if ($enabled) {
7196                  if ($updowncount > 1) {
7197                      $updown .= "<a href=\"$url&amp;action=up&amp;auth=$auth\">";
7198                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7199                  }
7200                  else {
7201                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7202                  }
7203                  if ($updowncount < $authcount) {
7204                      $updown .= "<a href=\"$url&amp;action=down&amp;auth=$auth\">";
7205                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7206                  }
7207                  else {
7208                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7209                  }
7210                  ++ $updowncount;
7211              }
7212  
7213              // settings link
7214              if (file_exists($CFG->dirroot.'/auth/'.$auth.'/settings.php')) {
7215                  $settings = "<a href=\"settings.php?section=authsetting$auth\">{$txt->settings}</a>";
7216              } else if (file_exists($CFG->dirroot.'/auth/'.$auth.'/config.html')) {
7217                  $settings = "<a href=\"auth_config.php?auth=$auth\">{$txt->settings}</a>";
7218              } else {
7219                  $settings = '';
7220              }
7221  
7222              // Uninstall link.
7223              $uninstall = '';
7224              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('auth_'.$auth, 'manage')) {
7225                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7226              }
7227  
7228              $test = '';
7229              if (!empty($authplugins[$auth]) and method_exists($authplugins[$auth], 'test_settings')) {
7230                  $testurl = new moodle_url('/auth/test_settings.php', array('auth'=>$auth, 'sesskey'=>sesskey()));
7231                  $test = html_writer::link($testurl, $txt->testsettings);
7232              }
7233  
7234              // Add a row to the table.
7235              $row = new html_table_row(array($displayname, $usercount, $hideshow, $updown, $settings, $test, $uninstall));
7236              if ($class) {
7237                  $row->attributes['class'] = $class;
7238              }
7239              $table->data[] = $row;
7240          }
7241          $return .= html_writer::table($table);
7242          $return .= get_string('configauthenticationplugins', 'admin').'<br />'.get_string('tablenosave', 'filters');
7243          $return .= $OUTPUT->box_end();
7244          return highlight($query, $return);
7245      }
7246  }
7247  
7248  
7249  /**
7250   * Special class for authentication administration.
7251   *
7252   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7253   */
7254  class admin_setting_manageeditors extends admin_setting {
7255      /**
7256       * Calls parent::__construct with specific arguments
7257       */
7258      public function __construct() {
7259          $this->nosave = true;
7260          parent::__construct('editorsui', get_string('editorsettings', 'editor'), '', '');
7261      }
7262  
7263      /**
7264       * Always returns true, does nothing
7265       *
7266       * @return true
7267       */
7268      public function get_setting() {
7269          return true;
7270      }
7271  
7272      /**
7273       * Always returns true, does nothing
7274       *
7275       * @return true
7276       */
7277      public function get_defaultsetting() {
7278          return true;
7279      }
7280  
7281      /**
7282       * Always returns '', does not write anything
7283       *
7284       * @return string Always returns ''
7285       */
7286      public function write_setting($data) {
7287      // do not write any setting
7288          return '';
7289      }
7290  
7291      /**
7292       * Checks if $query is one of the available editors
7293       *
7294       * @param string $query The string to search for
7295       * @return bool Returns true if found, false if not
7296       */
7297      public function is_related($query) {
7298          if (parent::is_related($query)) {
7299              return true;
7300          }
7301  
7302          $editors_available = editors_get_available();
7303          foreach ($editors_available as $editor=>$editorstr) {
7304              if (strpos($editor, $query) !== false) {
7305                  return true;
7306              }
7307              if (strpos(core_text::strtolower($editorstr), $query) !== false) {
7308                  return true;
7309              }
7310          }
7311          return false;
7312      }
7313  
7314      /**
7315       * Builds the XHTML to display the control
7316       *
7317       * @param string $data Unused
7318       * @param string $query
7319       * @return string
7320       */
7321      public function output_html($data, $query='') {
7322          global $CFG, $OUTPUT;
7323  
7324          // display strings
7325          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7326              'up', 'down', 'none'));
7327          $struninstall = get_string('uninstallplugin', 'core_admin');
7328  
7329          $txt->updown = "$txt->up/$txt->down";
7330  
7331          $editors_available = editors_get_available();
7332          $active_editors = explode(',', $CFG->texteditors);
7333  
7334          $active_editors = array_reverse($active_editors);
7335          foreach ($active_editors as $key=>$editor) {
7336              if (empty($editors_available[$editor])) {
7337                  unset($active_editors[$key]);
7338              } else {
7339                  $name = $editors_available[$editor];
7340                  unset($editors_available[$editor]);
7341                  $editors_available[$editor] = $name;
7342              }
7343          }
7344          if (empty($active_editors)) {
7345          //$active_editors = array('textarea');
7346          }
7347          $editors_available = array_reverse($editors_available, true);
7348          $return = $OUTPUT->heading(get_string('acteditorshhdr', 'editor'), 3, 'main', true);
7349          $return .= $OUTPUT->box_start('generalbox editorsui');
7350  
7351          $table = new html_table();
7352          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7353          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7354          $table->id = 'editormanagement';
7355          $table->attributes['class'] = 'admintable generaltable';
7356          $table->data  = array();
7357  
7358          // iterate through auth plugins and add to the display table
7359          $updowncount = 1;
7360          $editorcount = count($active_editors);
7361          $url = "editors.php?sesskey=" . sesskey();
7362          foreach ($editors_available as $editor => $name) {
7363          // hide/show link
7364              $class = '';
7365              if (in_array($editor, $active_editors)) {
7366                  $hideshow = "<a href=\"$url&amp;action=disable&amp;editor=$editor\">";
7367                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7368                  $enabled = true;
7369                  $displayname = $name;
7370              }
7371              else {
7372                  $hideshow = "<a href=\"$url&amp;action=enable&amp;editor=$editor\">";
7373                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7374                  $enabled = false;
7375                  $displayname = $name;
7376                  $class = 'dimmed_text';
7377              }
7378  
7379              // up/down link (only if auth is enabled)
7380              $updown = '';
7381              if ($enabled) {
7382                  if ($updowncount > 1) {
7383                      $updown .= "<a href=\"$url&amp;action=up&amp;editor=$editor\">";
7384                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7385                  }
7386                  else {
7387                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7388                  }
7389                  if ($updowncount < $editorcount) {
7390                      $updown .= "<a href=\"$url&amp;action=down&amp;editor=$editor\">";
7391                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7392                  }
7393                  else {
7394                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7395                  }
7396                  ++ $updowncount;
7397              }
7398  
7399              // settings link
7400              if (file_exists($CFG->dirroot.'/lib/editor/'.$editor.'/settings.php')) {
7401                  $eurl = new moodle_url('/admin/settings.php', array('section'=>'editorsettings'.$editor));
7402                  $settings = "<a href='$eurl'>{$txt->settings}</a>";
7403              } else {
7404                  $settings = '';
7405              }
7406  
7407              $uninstall = '';
7408              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('editor_'.$editor, 'manage')) {
7409                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7410              }
7411  
7412              // Add a row to the table.
7413              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7414              if ($class) {
7415                  $row->attributes['class'] = $class;
7416              }
7417              $table->data[] = $row;
7418          }
7419          $return .= html_writer::table($table);
7420          $return .= get_string('configeditorplugins', 'editor').'<br />'.get_string('tablenosave', 'admin');
7421          $return .= $OUTPUT->box_end();
7422          return highlight($query, $return);
7423      }
7424  }
7425  
7426  /**
7427   * Special class for antiviruses administration.
7428   *
7429   * @copyright  2015 Ruslan Kabalin, Lancaster University.
7430   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7431   */
7432  class admin_setting_manageantiviruses extends admin_setting {
7433      /**
7434       * Calls parent::__construct with specific arguments
7435       */
7436      public function __construct() {
7437          $this->nosave = true;
7438          parent::__construct('antivirusesui', get_string('antivirussettings', 'antivirus'), '', '');
7439      }
7440  
7441      /**
7442       * Always returns true, does nothing
7443       *
7444       * @return true
7445       */
7446      public function get_setting() {
7447          return true;
7448      }
7449  
7450      /**
7451       * Always returns true, does nothing
7452       *
7453       * @return true
7454       */
7455      public function get_defaultsetting() {
7456          return true;
7457      }
7458  
7459      /**
7460       * Always returns '', does not write anything
7461       *
7462       * @param string $data Unused
7463       * @return string Always returns ''
7464       */
7465      public function write_setting($data) {
7466          // Do not write any setting.
7467          return '';
7468      }
7469  
7470      /**
7471       * Checks if $query is one of the available editors
7472       *
7473       * @param string $query The string to search for
7474       * @return bool Returns true if found, false if not
7475       */
7476      public function is_related($query) {
7477          if (parent::is_related($query)) {
7478              return true;
7479          }
7480  
7481          $antivirusesavailable = \core\antivirus\manager::get_available();
7482          foreach ($antivirusesavailable as $antivirus => $antivirusstr) {
7483              if (strpos($antivirus, $query) !== false) {
7484                  return true;
7485              }
7486              if (strpos(core_text::strtolower($antivirusstr), $query) !== false) {
7487                  return true;
7488              }
7489          }
7490          return false;
7491      }
7492  
7493      /**
7494       * Builds the XHTML to display the control
7495       *
7496       * @param string $data Unused
7497       * @param string $query
7498       * @return string
7499       */
7500      public function output_html($data, $query='') {
7501          global $CFG, $OUTPUT;
7502  
7503          // Display strings.
7504          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7505              'up', 'down', 'none'));
7506          $struninstall = get_string('uninstallplugin', 'core_admin');
7507  
7508          $txt->updown = "$txt->up/$txt->down";
7509  
7510          $antivirusesavailable = \core\antivirus\manager::get_available();
7511          $activeantiviruses = explode(',', $CFG->antiviruses);
7512  
7513          $activeantiviruses = array_reverse($activeantiviruses);
7514          foreach ($activeantiviruses as $key => $antivirus) {
7515              if (empty($antivirusesavailable[$antivirus])) {
7516                  unset($activeantiviruses[$key]);
7517              } else {
7518                  $name = $antivirusesavailable[$antivirus];
7519                  unset($antivirusesavailable[$antivirus]);
7520                  $antivirusesavailable[$antivirus] = $name;
7521              }
7522          }
7523          $antivirusesavailable = array_reverse($antivirusesavailable, true);
7524          $return = $OUTPUT->heading(get_string('actantivirushdr', 'antivirus'), 3, 'main', true);
7525          $return .= $OUTPUT->box_start('generalbox antivirusesui');
7526  
7527          $table = new html_table();
7528          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7529          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7530          $table->id = 'antivirusmanagement';
7531          $table->attributes['class'] = 'admintable generaltable';
7532          $table->data  = array();
7533  
7534          // Iterate through auth plugins and add to the display table.
7535          $updowncount = 1;
7536          $antiviruscount = count($activeantiviruses);
7537          $baseurl = new moodle_url('/admin/antiviruses.php', array('sesskey' => sesskey()));
7538          foreach ($antivirusesavailable as $antivirus => $name) {
7539              // Hide/show link.
7540              $class = '';
7541              if (in_array($antivirus, $activeantiviruses)) {
7542                  $hideshowurl = $baseurl;
7543                  $hideshowurl->params(array('action' => 'disable', 'antivirus' => $antivirus));
7544                  $hideshowimg = $OUTPUT->pix_icon('t/hide', get_string('disable'));
7545                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7546                  $enabled = true;
7547                  $displayname = $name;
7548              } else {
7549                  $hideshowurl = $baseurl;
7550                  $hideshowurl->params(array('action' => 'enable', 'antivirus' => $antivirus));
7551                  $hideshowimg = $OUTPUT->pix_icon('t/show', get_string('enable'));
7552                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7553                  $enabled = false;
7554                  $displayname = $name;
7555                  $class = 'dimmed_text';
7556              }
7557  
7558              // Up/down link.
7559              $updown = '';
7560              if ($enabled) {
7561                  if ($updowncount > 1) {
7562                      $updownurl = $baseurl;
7563                      $updownurl->params(array('action' => 'up', 'antivirus' => $antivirus));
7564                      $updownimg = $OUTPUT->pix_icon('t/up', get_string('moveup'));
7565                      $updown = html_writer::link($updownurl, $updownimg);
7566                  } else {
7567                      $updownimg = $OUTPUT->spacer();
7568                  }
7569                  if ($updowncount < $antiviruscount) {
7570                      $updownurl = $baseurl;
7571                      $updownurl->params(array('action' => 'down', 'antivirus' => $antivirus));
7572                      $updownimg = $OUTPUT->pix_icon('t/down', get_string('movedown'));
7573                      $updown = html_writer::link($updownurl, $updownimg);
7574                  } else {
7575                      $updownimg = $OUTPUT->spacer();
7576                  }
7577                  ++ $updowncount;
7578              }
7579  
7580              // Settings link.
7581              if (file_exists($CFG->dirroot.'/lib/antivirus/'.$antivirus.'/settings.php')) {
7582                  $eurl = new moodle_url('/admin/settings.php', array('section' => 'antivirussettings'.$antivirus));
7583                  $settings = html_writer::link($eurl, $txt->settings);
7584              } else {
7585                  $settings = '';
7586              }
7587  
7588              $uninstall = '';
7589              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('antivirus_'.$antivirus, 'manage')) {
7590                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7591              }
7592  
7593              // Add a row to the table.
7594              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7595              if ($class) {
7596                  $row->attributes['class'] = $class;
7597              }
7598              $table->data[] = $row;
7599          }
7600          $return .= html_writer::table($table);
7601          $return .= get_string('configantivirusplugins', 'antivirus') . html_writer::empty_tag('br') . get_string('tablenosave', 'admin');
7602          $return .= $OUTPUT->box_end();
7603          return highlight($query, $return);
7604      }
7605  }
7606  
7607  /**
7608   * Special class for license administration.
7609   *
7610   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7611   * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7612   * @todo MDL-45184 This class will be deleted in Moodle 4.1.
7613   */
7614  class admin_setting_managelicenses extends admin_setting {
7615      /**
7616       * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7617       * @todo MDL-45184 This class will be deleted in Moodle 4.1
7618       */
7619      public function __construct() {
7620          global $ADMIN;
7621  
7622          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7623              DEBUG_DEVELOPER);
7624  
7625          // Replace admin setting load with new external page load for tool_licensemanager, if not loaded already.
7626          if (!is_null($ADMIN->locate('licensemanager'))) {
7627              $temp = new admin_externalpage('licensemanager',
7628                  get_string('licensemanager', 'tool_licensemanager'),
7629                  \tool_licensemanager\helper::get_licensemanager_url());
7630  
7631              $ADMIN->add('license', $temp);
7632          }
7633      }
7634  
7635      /**
7636       * Always returns true, does nothing
7637       *
7638       * @deprecated since Moodle 3.9 MDL-45184.
7639       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7640       *
7641       * @return true
7642       */
7643      public function get_setting() {
7644          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7645              DEBUG_DEVELOPER);
7646  
7647          return true;
7648      }
7649  
7650      /**
7651       * Always returns true, does nothing
7652       *
7653       * @deprecated since Moodle 3.9 MDL-45184.
7654       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7655       *
7656       * @return true
7657       */
7658      public function get_defaultsetting() {
7659          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7660              DEBUG_DEVELOPER);
7661  
7662          return true;
7663      }
7664  
7665      /**
7666       * Always returns '', does not write anything
7667       *
7668       * @deprecated since Moodle 3.9 MDL-45184.
7669       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7670       *
7671       * @return string Always returns ''
7672       */
7673      public function write_setting($data) {
7674          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7675              DEBUG_DEVELOPER);
7676  
7677          // do not write any setting
7678          return '';
7679      }
7680  
7681      /**
7682       * Builds the XHTML to display the control
7683       *
7684       * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7685       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7686       *
7687       * @param string $data Unused
7688       * @param string $query
7689       * @return string
7690       */
7691      public function output_html($data, $query='') {
7692          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7693              DEBUG_DEVELOPER);
7694  
7695          redirect(\tool_licensemanager\helper::get_licensemanager_url());
7696      }
7697  }
7698  
7699  /**
7700   * Course formats manager. Allows to enable/disable formats and jump to settings
7701   */
7702  class admin_setting_manageformats extends admin_setting {
7703  
7704      /**
7705       * Calls parent::__construct with specific arguments
7706       */
7707      public function __construct() {
7708          $this->nosave = true;
7709          parent::__construct('formatsui', new lang_string('manageformats', 'core_admin'), '', '');
7710      }
7711  
7712      /**
7713       * Always returns true
7714       *
7715       * @return true
7716       */
7717      public function get_setting() {
7718          return true;
7719      }
7720  
7721      /**
7722       * Always returns true
7723       *
7724       * @return true
7725       */
7726      public function get_defaultsetting() {
7727          return true;
7728      }
7729  
7730      /**
7731       * Always returns '' and doesn't write anything
7732       *
7733       * @param mixed $data string or array, must not be NULL
7734       * @return string Always returns ''
7735       */
7736      public function write_setting($data) {
7737          // do not write any setting
7738          return '';
7739      }
7740  
7741      /**
7742       * Search to find if Query is related to format plugin
7743       *
7744       * @param string $query The string to search for
7745       * @return bool true for related false for not
7746       */
7747      public function is_related($query) {
7748          if (parent::is_related($query)) {
7749              return true;
7750          }
7751          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7752          foreach ($formats as $format) {
7753              if (strpos($format->component, $query) !== false ||
7754                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7755                  return true;
7756              }
7757          }
7758          return false;
7759      }
7760  
7761      /**
7762       * Return XHTML to display control
7763       *
7764       * @param mixed $data Unused
7765       * @param string $query
7766       * @return string highlight
7767       */
7768      public function output_html($data, $query='') {
7769          global $CFG, $OUTPUT;
7770          $return = '';
7771          $return = $OUTPUT->heading(new lang_string('courseformats'), 3, 'main');
7772          $return .= $OUTPUT->box_start('generalbox formatsui');
7773  
7774          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7775  
7776          // display strings
7777          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
7778          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7779          $txt->updown = "$txt->up/$txt->down";
7780  
7781          $table = new html_table();
7782          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
7783          $table->align = array('left', 'center', 'center', 'center', 'center');
7784          $table->attributes['class'] = 'manageformattable generaltable admintable';
7785          $table->data  = array();
7786  
7787          $cnt = 0;
7788          $defaultformat = get_config('moodlecourse', 'format');
7789          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7790          foreach ($formats as $format) {
7791              $url = new moodle_url('/admin/courseformats.php',
7792                      array('sesskey' => sesskey(), 'format' => $format->name));
7793              $isdefault = '';
7794              $class = '';
7795              if ($format->is_enabled()) {
7796                  $strformatname = $format->displayname;
7797                  if ($defaultformat === $format->name) {
7798                      $hideshow = $txt->default;
7799                  } else {
7800                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7801                              $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7802                  }
7803              } else {
7804                  $strformatname = $format->displayname;
7805                  $class = 'dimmed_text';
7806                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7807                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7808              }
7809              $updown = '';
7810              if ($cnt) {
7811                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
7812                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
7813              } else {
7814                  $updown .= $spacer;
7815              }
7816              if ($cnt < count($formats) - 1) {
7817                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
7818                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
7819              } else {
7820                  $updown .= $spacer;
7821              }
7822              $cnt++;
7823              $settings = '';
7824              if ($format->get_settings_url()) {
7825                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
7826              }
7827              $uninstall = '';
7828              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('format_'.$format->name, 'manage')) {
7829                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7830              }
7831              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
7832              if ($class) {
7833                  $row->attributes['class'] = $class;
7834              }
7835              $table->data[] = $row;
7836          }
7837          $return .= html_writer::table($table);
7838          $link = html_writer::link(new moodle_url('/admin/settings.php', array('section' => 'coursesettings')), new lang_string('coursesettings'));
7839          $return .= html_writer::tag('p', get_string('manageformatsgotosettings', 'admin', $link));
7840          $return .= $OUTPUT->box_end();
7841          return highlight($query, $return);
7842      }
7843  }
7844  
7845  /**
7846   * Custom fields manager. Allows to enable/disable custom fields and jump to settings.
7847   *
7848   * @package    core
7849   * @copyright  2018 Toni Barbera
7850   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7851   */
7852  class admin_setting_managecustomfields extends admin_setting {
7853  
7854      /**
7855       * Calls parent::__construct with specific arguments
7856       */
7857      public function __construct() {
7858          $this->nosave = true;
7859          parent::__construct('customfieldsui', new lang_string('managecustomfields', 'core_admin'), '', '');
7860      }
7861  
7862      /**
7863       * Always returns true
7864       *
7865       * @return true
7866       */
7867      public function get_setting() {
7868          return true;
7869      }
7870  
7871      /**
7872       * Always returns true
7873       *
7874       * @return true
7875       */
7876      public function get_defaultsetting() {
7877          return true;
7878      }
7879  
7880      /**
7881       * Always returns '' and doesn't write anything
7882       *
7883       * @param mixed $data string or array, must not be NULL
7884       * @return string Always returns ''
7885       */
7886      public function write_setting($data) {
7887          // Do not write any setting.
7888          return '';
7889      }
7890  
7891      /**
7892       * Search to find if Query is related to format plugin
7893       *
7894       * @param string $query The string to search for
7895       * @return bool true for related false for not
7896       */
7897      public function is_related($query) {
7898          if (parent::is_related($query)) {
7899              return true;
7900          }
7901          $formats = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7902          foreach ($formats as $format) {
7903              if (strpos($format->component, $query) !== false ||
7904                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7905                  return true;
7906              }
7907          }
7908          return false;
7909      }
7910  
7911      /**
7912       * Return XHTML to display control
7913       *
7914       * @param mixed $data Unused
7915       * @param string $query
7916       * @return string highlight
7917       */
7918      public function output_html($data, $query='') {
7919          global $CFG, $OUTPUT;
7920          $return = '';
7921          $return = $OUTPUT->heading(new lang_string('customfields', 'core_customfield'), 3, 'main');
7922          $return .= $OUTPUT->box_start('generalbox customfieldsui');
7923  
7924          $fields = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7925  
7926          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down'));
7927          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7928          $txt->updown = "$txt->up/$txt->down";
7929  
7930          $table = new html_table();
7931          $table->head  = array($txt->name, $txt->enable, $txt->uninstall, $txt->settings);
7932          $table->align = array('left', 'center', 'center', 'center');
7933          $table->attributes['class'] = 'managecustomfieldtable generaltable admintable';
7934          $table->data  = array();
7935  
7936          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7937          foreach ($fields as $field) {
7938              $url = new moodle_url('/admin/customfields.php',
7939                      array('sesskey' => sesskey(), 'field' => $field->name));
7940  
7941              if ($field->is_enabled()) {
7942                  $strfieldname = $field->displayname;
7943                  $class = '';
7944                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7945                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7946              } else {
7947                  $strfieldname = $field->displayname;
7948                  $class = 'dimmed_text';
7949                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7950                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7951              }
7952              $settings = '';
7953              if ($field->get_settings_url()) {
7954                  $settings = html_writer::link($field->get_settings_url(), $txt->settings);
7955              }
7956              $uninstall = '';
7957              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('customfield_'.$field->name, 'manage')) {
7958                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7959              }
7960              $row = new html_table_row(array($strfieldname, $hideshow, $uninstall, $settings));
7961              $row->attributes['class'] = $class;
7962              $table->data[] = $row;
7963          }
7964          $return .= html_writer::table($table);
7965          $return .= $OUTPUT->box_end();
7966          return highlight($query, $return);
7967      }
7968  }
7969  
7970  /**
7971   * Data formats manager. Allow reorder and to enable/disable data formats and jump to settings
7972   *
7973   * @copyright  2016 Brendan Heywood (brendan@catalyst-au.net)
7974   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7975   */
7976  class admin_setting_managedataformats extends admin_setting {
7977  
7978      /**
7979       * Calls parent::__construct with specific arguments
7980       */
7981      public function __construct() {
7982          $this->nosave = true;
7983          parent::__construct('managedataformats', new lang_string('managedataformats'), '', '');
7984      }
7985  
7986      /**
7987       * Always returns true
7988       *
7989       * @return true
7990       */
7991      public function get_setting() {
7992          return true;
7993      }
7994  
7995      /**
7996       * Always returns true
7997       *
7998       * @return true
7999       */
8000      public function get_defaultsetting() {
8001          return true;
8002      }
8003  
8004      /**
8005       * Always returns '' and doesn't write anything
8006       *
8007       * @param mixed $data string or array, must not be NULL
8008       * @return string Always returns ''
8009       */
8010      public function write_setting($data) {
8011          // Do not write any setting.
8012          return '';
8013      }
8014  
8015      /**
8016       * Search to find if Query is related to format plugin
8017       *
8018       * @param string $query The string to search for
8019       * @return bool true for related false for not
8020       */
8021      public function is_related($query) {
8022          if (parent::is_related($query)) {
8023              return true;
8024          }
8025          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
8026          foreach ($formats as $format) {
8027              if (strpos($format->component, $query) !== false ||
8028                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
8029                  return true;
8030              }
8031          }
8032          return false;
8033      }
8034  
8035      /**
8036       * Return XHTML to display control
8037       *
8038       * @param mixed $data Unused
8039       * @param string $query
8040       * @return string highlight
8041       */
8042      public function output_html($data, $query='') {
8043          global $CFG, $OUTPUT;
8044          $return = '';
8045  
8046          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
8047  
8048          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
8049          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
8050          $txt->updown = "$txt->up/$txt->down";
8051  
8052          $table = new html_table();
8053          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
8054          $table->align = array('left', 'center', 'center', 'center', 'center');
8055          $table->attributes['class'] = 'manageformattable generaltable admintable';
8056          $table->data  = array();
8057  
8058          $cnt = 0;
8059          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8060          $totalenabled = 0;
8061          foreach ($formats as $format) {
8062              if ($format->is_enabled() && $format->is_installed_and_upgraded()) {
8063                  $totalenabled++;
8064              }
8065          }
8066          foreach ($formats as $format) {
8067              $status = $format->get_status();
8068              $url = new moodle_url('/admin/dataformats.php',
8069                      array('sesskey' => sesskey(), 'name' => $format->name));
8070  
8071              $class = '';
8072              if ($format->is_enabled()) {
8073                  $strformatname = $format->displayname;
8074                  if ($totalenabled == 1&& $format->is_enabled()) {
8075                      $hideshow = '';
8076                  } else {
8077                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
8078                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
8079                  }
8080              } else {
8081                  $class = 'dimmed_text';
8082                  $strformatname = $format->displayname;
8083                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
8084                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
8085              }
8086  
8087              $updown = '';
8088              if ($cnt) {
8089                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
8090                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
8091              } else {
8092                  $updown .= $spacer;
8093              }
8094              if ($cnt < count($formats) - 1) {
8095                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
8096                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
8097              } else {
8098                  $updown .= $spacer;
8099              }
8100  
8101              $uninstall = '';
8102              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
8103                  $uninstall = get_string('status_missing', 'core_plugin');
8104              } else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
8105                  $uninstall = get_string('status_new', 'core_plugin');
8106              } else if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('dataformat_'.$format->name, 'manage')) {
8107                  if ($totalenabled != 1 || !$format->is_enabled()) {
8108                      $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
8109                  }
8110              }
8111  
8112              $settings = '';
8113              if ($format->get_settings_url()) {
8114                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
8115              }
8116  
8117              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
8118              if ($class) {
8119                  $row->attributes['class'] = $class;
8120              }
8121              $table->data[] = $row;
8122              $cnt++;
8123          }
8124          $return .= html_writer::table($table);
8125          return highlight($query, $return);
8126      }
8127  }
8128  
8129  /**
8130   * Special class for filter administration.
8131   *
8132   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8133   */
8134  class admin_page_managefilters extends admin_externalpage {
8135      /**
8136       * Calls parent::__construct with specific arguments
8137       */
8138      public function __construct() {
8139          global $CFG;
8140          parent::__construct('managefilters', get_string('filtersettings', 'admin'), "$CFG->wwwroot/$CFG->admin/filters.php");
8141      }
8142  
8143      /**
8144       * Searches all installed filters for specified filter
8145       *
8146       * @param string $query The filter(string) to search for
8147       * @param string $query
8148       */
8149      public function search($query) {
8150          global $CFG;
8151          if ($result = parent::search($query)) {
8152              return $result;
8153          }
8154  
8155          $found = false;
8156          $filternames = filter_get_all_installed();
8157          foreach ($filternames as $path => $strfiltername) {
8158              if (strpos(core_text::strtolower($strfiltername), $query) !== false) {
8159                  $found = true;
8160                  break;
8161              }
8162              if (strpos($path, $query) !== false) {
8163                  $found = true;
8164                  break;
8165              }
8166          }
8167  
8168          if ($found) {
8169              $result = new stdClass;
8170              $result->page = $this;
8171              $result->settings = array();
8172              return array($this->name => $result);
8173          } else {
8174              return array();
8175          }
8176      }
8177  }
8178  
8179  /**
8180   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8181   * Requires a get_rank method on the plugininfo class for sorting.
8182   *
8183   * @copyright 2017 Damyon Wiese
8184   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8185   */
8186  abstract class admin_setting_manage_plugins extends admin_setting {
8187  
8188      /**
8189       * Get the admin settings section name (just a unique string)
8190       *
8191       * @return string
8192       */
8193      public function get_section_name() {
8194          return 'manage' . $this->get_plugin_type() . 'plugins';
8195      }
8196  
8197      /**
8198       * Get the admin settings section title (use get_string).
8199       *
8200       * @return string
8201       */
8202      abstract public function get_section_title();
8203  
8204      /**
8205       * Get the type of plugin to manage.
8206       *
8207       * @return string
8208       */
8209      abstract public function get_plugin_type();
8210  
8211      /**
8212       * Get the name of the second column.
8213       *
8214       * @return string
8215       */
8216      public function get_info_column_name() {
8217          return '';
8218      }
8219  
8220      /**
8221       * Get the type of plugin to manage.
8222       *
8223       * @param plugininfo The plugin info class.
8224       * @return string
8225       */
8226      abstract public function get_info_column($plugininfo);
8227  
8228      /**
8229       * Calls parent::__construct with specific arguments
8230       */
8231      public function __construct() {
8232          $this->nosave = true;
8233          parent::__construct($this->get_section_name(), $this->get_section_title(), '', '');
8234      }
8235  
8236      /**
8237       * Always returns true, does nothing
8238       *
8239       * @return true
8240       */
8241      public function get_setting() {
8242          return true;
8243      }
8244  
8245      /**
8246       * Always returns true, does nothing
8247       *
8248       * @return true
8249       */
8250      public function get_defaultsetting() {
8251          return true;
8252      }
8253  
8254      /**
8255       * Always returns '', does not write anything
8256       *
8257       * @param mixed $data
8258       * @return string Always returns ''
8259       */
8260      public function write_setting($data) {
8261          // Do not write any setting.
8262          return '';
8263      }
8264  
8265      /**
8266       * Checks if $query is one of the available plugins of this type
8267       *
8268       * @param string $query The string to search for
8269       * @return bool Returns true if found, false if not
8270       */
8271      public function is_related($query) {
8272          if (parent::is_related($query)) {
8273              return true;
8274          }
8275  
8276          $query = core_text::strtolower($query);
8277          $plugins = core_plugin_manager::instance()->get_plugins_of_type($this->get_plugin_type());
8278          foreach ($plugins as $name => $plugin) {
8279              $localised = $plugin->displayname;
8280              if (strpos(core_text::strtolower($name), $query) !== false) {
8281                  return true;
8282              }
8283              if (strpos(core_text::strtolower($localised), $query) !== false) {
8284                  return true;
8285              }
8286          }
8287          return false;
8288      }
8289  
8290      /**
8291       * The URL for the management page for this plugintype.
8292       *
8293       * @return moodle_url
8294       */
8295      protected function get_manage_url() {
8296          return new moodle_url('/admin/updatesetting.php');
8297      }
8298  
8299      /**
8300       * Builds the HTML to display the control.
8301       *
8302       * @param string $data Unused
8303       * @param string $query
8304       * @return string
8305       */
8306      public function output_html($data, $query = '') {
8307          global $CFG, $OUTPUT, $DB, $PAGE;
8308  
8309          $context = (object) [
8310              'manageurl' => new moodle_url($this->get_manage_url(), [
8311                      'type' => $this->get_plugin_type(),
8312                      'sesskey' => sesskey(),
8313                  ]),
8314              'infocolumnname' => $this->get_info_column_name(),
8315              'plugins' => [],
8316          ];
8317  
8318          $pluginmanager = core_plugin_manager::instance();
8319          $allplugins = $pluginmanager->get_plugins_of_type($this->get_plugin_type());
8320          $enabled = $pluginmanager->get_enabled_plugins($this->get_plugin_type());
8321          $plugins = array_merge($enabled, $allplugins);
8322          foreach ($plugins as $key => $plugin) {
8323              $pluginlink = new moodle_url($context->manageurl, ['plugin' => $key]);
8324  
8325              $pluginkey = (object) [
8326                  'plugin' => $plugin->displayname,
8327                  'enabled' => $plugin->is_enabled(),
8328                  'togglelink' => '',
8329                  'moveuplink' => '',
8330                  'movedownlink' => '',
8331                  'settingslink' => $plugin->get_settings_url(),
8332                  'uninstalllink' => '',
8333                  'info' => '',
8334              ];
8335  
8336              // Enable/Disable link.
8337              $togglelink = new moodle_url($pluginlink);
8338              if ($plugin->is_enabled()) {
8339                  $toggletarget = false;
8340                  $togglelink->param('action', 'disable');
8341  
8342                  if (count($context->plugins)) {
8343                      // This is not the first plugin.
8344                      $pluginkey->moveuplink = new moodle_url($pluginlink, ['action' => 'up']);
8345                  }
8346  
8347                  if (count($enabled) > count($context->plugins) + 1) {
8348                      // This is not the last plugin.
8349                      $pluginkey->movedownlink = new moodle_url($pluginlink, ['action' => 'down']);
8350                  }
8351  
8352                  $pluginkey->info = $this->get_info_column($plugin);
8353              } else {
8354                  $toggletarget = true;
8355                  $togglelink->param('action', 'enable');
8356              }
8357  
8358              $pluginkey->toggletarget = $toggletarget;
8359              $pluginkey->togglelink = $togglelink;
8360  
8361              $frankenstyle = $plugin->type . '_' . $plugin->name;
8362              if ($uninstalllink = core_plugin_manager::instance()->get_uninstall_url($frankenstyle, 'manage')) {
8363                  // This plugin supports uninstallation.
8364                  $pluginkey->uninstalllink = $uninstalllink;
8365              }
8366  
8367              if (!empty($this->get_info_column_name())) {
8368                  // This plugintype has an info column.
8369                  $pluginkey->info = $this->get_info_column($plugin);
8370              }
8371  
8372              $context->plugins[] = $pluginkey;
8373          }
8374  
8375          $str = $OUTPUT->render_from_template('core_admin/setting_manage_plugins', $context);
8376          return highlight($query, $str);
8377      }
8378  }
8379  
8380  /**
8381   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8382   * Requires a get_rank method on the plugininfo class for sorting.
8383   *
8384   * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
8385  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8386   */
8387  class admin_setting_manage_fileconverter_plugins extends admin_setting_manage_plugins {
8388      public function get_section_title() {
8389          return get_string('type_fileconverter_plural', 'plugin');
8390      }
8391  
8392      public function get_plugin_type() {
8393          return 'fileconverter';
8394      }
8395  
8396      public function get_info_column_name() {
8397          return get_string('supportedconversions', 'plugin');
8398      }
8399  
8400      public function get_info_column($plugininfo) {
8401          return $plugininfo->get_supported_conversions();
8402      }
8403  }
8404  
8405  /**
8406   * Special class for media player plugins management.
8407   *
8408   * @copyright 2016 Marina Glancy
8409   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8410   */
8411  class admin_setting_managemediaplayers extends admin_setting {
8412      /**
8413       * Calls parent::__construct with specific arguments
8414       */
8415      public function __construct() {
8416          $this->nosave = true;
8417          parent::__construct('managemediaplayers', get_string('managemediaplayers', 'media'), '', '');
8418      }
8419  
8420      /**
8421       * Always returns true, does nothing
8422       *
8423       * @return true
8424       */
8425      public function get_setting() {
8426          return true;
8427      }
8428  
8429      /**
8430       * Always returns true, does nothing
8431       *
8432       * @return true
8433       */
8434      public function get_defaultsetting() {
8435          return true;
8436      }
8437  
8438      /**
8439       * Always returns '', does not write anything
8440       *
8441       * @param mixed $data
8442       * @return string Always returns ''
8443       */
8444      public function write_setting($data) {
8445          // Do not write any setting.
8446          return '';
8447      }
8448  
8449      /**
8450       * Checks if $query is one of the available enrol plugins
8451       *
8452       * @param string $query The string to search for
8453       * @return bool Returns true if found, false if not
8454       */
8455      public function is_related($query) {
8456          if (parent::is_related($query)) {
8457              return true;
8458          }
8459  
8460          $query = core_text::strtolower($query);
8461          $plugins = core_plugin_manager::instance()->get_plugins_of_type('media');
8462          foreach ($plugins as $name => $plugin) {
8463              $localised = $plugin->displayname;
8464              if (strpos(core_text::strtolower($name), $query) !== false) {
8465                  return true;
8466              }
8467              if (strpos(core_text::strtolower($localised), $query) !== false) {
8468                  return true;
8469              }
8470          }
8471          return false;
8472      }
8473  
8474      /**
8475       * Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8476       * @return \core\plugininfo\media[]
8477       */
8478      protected function get_sorted_plugins() {
8479          $pluginmanager = core_plugin_manager::instance();
8480  
8481          $plugins = $pluginmanager->get_plugins_of_type('media');
8482          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8483  
8484          // Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8485          \core_collator::asort_objects_by_method($plugins, 'get_rank', \core_collator::SORT_NUMERIC);
8486  
8487          $order = array_values($enabledplugins);
8488          $order = array_merge($order, array_diff(array_reverse(array_keys($plugins)), $order));
8489  
8490          $sortedplugins = array();
8491          foreach ($order as $name) {
8492              $sortedplugins[$name] = $plugins[$name];
8493          }
8494  
8495          return $sortedplugins;
8496      }
8497  
8498      /**
8499       * Builds the XHTML to display the control
8500       *
8501       * @param string $data Unused
8502       * @param string $query
8503       * @return string
8504       */
8505      public function output_html($data, $query='') {
8506          global $CFG, $OUTPUT, $DB, $PAGE;
8507  
8508          // Display strings.
8509          $strup        = get_string('up');
8510          $strdown      = get_string('down');
8511          $strsettings  = get_string('settings');
8512          $strenable    = get_string('enable');
8513          $strdisable   = get_string('disable');
8514          $struninstall = get_string('uninstallplugin', 'core_admin');
8515          $strversion   = get_string('version');
8516          $strname      = get_string('name');
8517          $strsupports  = get_string('supports', 'core_media');
8518  
8519          $pluginmanager = core_plugin_manager::instance();
8520  
8521          $plugins = $this->get_sorted_plugins();
8522          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8523  
8524          $return = $OUTPUT->box_start('generalbox mediaplayersui');
8525  
8526          $table = new html_table();
8527          $table->head  = array($strname, $strsupports, $strversion,
8528              $strenable, $strup.'/'.$strdown, $strsettings, $struninstall);
8529          $table->colclasses = array('leftalign', 'leftalign', 'centeralign',
8530              'centeralign', 'centeralign', 'centeralign', 'centeralign');
8531          $table->id = 'mediaplayerplugins';
8532          $table->attributes['class'] = 'admintable generaltable';
8533          $table->data  = array();
8534  
8535          // Iterate through media plugins and add to the display table.
8536          $updowncount = 1;
8537          $url = new moodle_url('/admin/media.php', array('sesskey' => sesskey()));
8538          $printed = array();
8539          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8540  
8541          $usedextensions = [];
8542          foreach ($plugins as $name => $plugin) {
8543              $url->param('media', $name);
8544              $plugininfo = $pluginmanager->get_plugin_info('media_'.$name);
8545              $version = $plugininfo->versiondb;
8546              $supports = $plugininfo->supports($usedextensions);
8547  
8548              // Hide/show links.
8549              $class = '';
8550              if (!$plugininfo->is_installed_and_upgraded()) {
8551                  $hideshow = '';
8552                  $enabled = false;
8553                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
8554              } else {
8555                  $enabled = $plugininfo->is_enabled();
8556                  if ($enabled) {
8557                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'disable')),
8558                          $OUTPUT->pix_icon('t/hide', $strdisable, 'moodle', array('class' => 'iconsmall')));
8559                  } else {
8560                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'enable')),
8561                          $OUTPUT->pix_icon('t/show', $strenable, 'moodle', array('class' => 'iconsmall')));
8562                      $class = 'dimmed_text';
8563                  }
8564                  $displayname = $plugin->displayname;
8565                  if (get_string_manager()->string_exists('pluginname_help', 'media_' . $name)) {
8566                      $displayname .= '&nbsp;' . $OUTPUT->help_icon('pluginname', 'media_' . $name);
8567                  }
8568              }
8569              if ($PAGE->theme->resolve_image_location('icon', 'media_' . $name, false)) {
8570                  $icon = $OUTPUT->pix_icon('icon', '', 'media_' . $name, array('class' => 'icon pluginicon'));
8571              } else {
8572                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
8573              }
8574  
8575              // Up/down link (only if enrol is enabled).
8576              $updown = '';
8577              if ($enabled) {
8578                  if ($updowncount > 1) {
8579                      $updown = html_writer::link(new moodle_url($url, array('action' => 'up')),
8580                          $OUTPUT->pix_icon('t/up', $strup, 'moodle', array('class' => 'iconsmall')));
8581                  } else {
8582                      $updown = $spacer;
8583                  }
8584                  if ($updowncount < count($enabledplugins)) {
8585                      $updown .= html_writer::link(new moodle_url($url, array('action' => 'down')),
8586                          $OUTPUT->pix_icon('t/down', $strdown, 'moodle', array('class' => 'iconsmall')));
8587                  } else {
8588                      $updown .= $spacer;
8589                  }
8590                  ++$updowncount;
8591              }
8592  
8593              $uninstall = '';
8594              $status = $plugininfo->get_status();
8595              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
8596                  $uninstall = get_string('status_missing', 'core_plugin') . '<br/>';
8597              }
8598              if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
8599                  $uninstall = get_string('status_new', 'core_plugin');
8600              } else if ($uninstallurl = $pluginmanager->get_uninstall_url('media_'.$name, 'manage')) {
8601                  $uninstall .= html_writer::link($uninstallurl, $struninstall);
8602              }
8603  
8604              $settings = '';
8605              if ($plugininfo->get_settings_url()) {
8606                  $settings = html_writer::link($plugininfo->get_settings_url(), $strsettings);
8607              }
8608  
8609              // Add a row to the table.
8610              $row = new html_table_row(array($icon.$displayname, $supports, $version, $hideshow, $updown, $settings, $uninstall));
8611              if ($class) {
8612                  $row->attributes['class'] = $class;
8613              }
8614              $table->data[] = $row;
8615  
8616              $printed[$name] = true;
8617          }
8618  
8619          $return .= html_writer::table($table);
8620          $return .= $OUTPUT->box_end();
8621          return highlight($query, $return);
8622      }
8623  }
8624  
8625  
8626  /**
8627   * Content bank content types manager. Allow reorder and to enable/disable content bank content types and jump to settings
8628   *
8629   * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
8630   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8631   */
8632  class admin_setting_managecontentbankcontenttypes extends admin_setting {
8633  
8634      /**
8635       * Calls parent::__construct with specific arguments
8636       */
8637      public function __construct() {
8638          $this->nosave = true;
8639          parent::__construct('contentbank', new lang_string('managecontentbanktypes'), '', '');
8640      }
8641  
8642      /**
8643       * Always returns true
8644       *
8645       * @return true
8646       */
8647      public function get_setting() {
8648          return true;
8649      }
8650  
8651      /**
8652       * Always returns true
8653       *
8654       * @return true
8655       */
8656      public function get_defaultsetting() {
8657          return true;
8658      }
8659  
8660      /**
8661       * Always returns '' and doesn't write anything
8662       *
8663       * @param mixed $data string or array, must not be NULL
8664       * @return string Always returns ''
8665       */
8666      public function write_setting($data) {
8667          // Do not write any setting.
8668          return '';
8669      }
8670  
8671      /**
8672       * Search to find if Query is related to content bank plugin
8673       *
8674       * @param string $query The string to search for
8675       * @return bool true for related false for not
8676       */
8677      public function is_related($query) {
8678          if (parent::is_related($query)) {
8679              return true;
8680          }
8681          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8682          foreach ($types as $type) {
8683              if (strpos($type->component, $query) !== false ||
8684                  strpos(core_text::strtolower($type->displayname), $query) !== false) {
8685                  return true;
8686              }
8687          }
8688          return false;
8689      }
8690  
8691      /**
8692       * Return XHTML to display control
8693       *
8694       * @param mixed $data Unused
8695       * @param string $query
8696       * @return string highlight
8697       */
8698      public function output_html($data, $query='') {
8699          global $CFG, $OUTPUT;
8700          $return = '';
8701  
8702          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8703          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'order', 'up', 'down', 'default'));
8704          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
8705  
8706          $table = new html_table();
8707          $table->head  = array($txt->name, $txt->enable, $txt->order, $txt->settings, $txt->uninstall);
8708          $table->align = array('left', 'center', 'center', 'center', 'center');
8709          $table->attributes['class'] = 'managecontentbanktable generaltable admintable';
8710          $table->data  = array();
8711          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8712  
8713          $totalenabled = 0;
8714          $count = 0;
8715          foreach ($types as $type) {
8716              if ($type->is_enabled() && $type->is_installed_and_upgraded()) {
8717                  $totalenabled++;
8718              }
8719          }
8720  
8721          foreach ($types as $type) {
8722              $url = new moodle_url('/admin/contentbank.php',
8723                  array('sesskey' => sesskey(), 'name' => $type->name));
8724  
8725              $class = '';
8726              $strtypename = $type->displayname;
8727              if ($type->is_enabled()) {
8728                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
8729                      $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
8730              } else {
8731                  $class = 'dimmed_text';
8732                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
8733                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
8734              }
8735  
8736              $updown = '';
8737              if ($count) {
8738                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
8739                          $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
8740              } else {
8741                  $updown .= $spacer;
8742              }
8743              if ($count < count($types) - 1) {
8744                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
8745                          $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
8746              } else {
8747                  $updown .= $spacer;
8748              }
8749  
8750              $settings = '';
8751              if ($type->get_settings_url()) {
8752                  $settings = html_writer::link($type->get_settings_url(), $txt->settings);
8753              }
8754  
8755              $uninstall = '';
8756              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('contenttype_'.$type->name, 'manage')) {
8757                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
8758              }
8759  
8760              $row = new html_table_row(array($strtypename, $hideshow, $updown, $settings, $uninstall));
8761              if ($class) {
8762                  $row->attributes['class'] = $class;
8763              }
8764              $table->data[] = $row;
8765              $count++;
8766          }
8767          $return .= html_writer::table($table);
8768          return highlight($query, $return);
8769      }
8770  }
8771  
8772  /**
8773   * Initialise admin page - this function does require login and permission
8774   * checks specified in page definition.
8775   *
8776   * This function must be called on each admin page before other code.
8777   *
8778   * @global moodle_page $PAGE
8779   *
8780   * @param string $section name of page
8781   * @param string $extrabutton extra HTML that is added after the blocks editing on/off button.
8782   * @param array $extraurlparams an array paramname => paramvalue, or parameters that need to be
8783   *      added to the turn blocks editing on/off form, so this page reloads correctly.
8784   * @param string $actualurl if the actual page being viewed is not the normal one for this
8785   *      page (e.g. admin/roles/allow.php, instead of admin/roles/manage.php, you can pass the alternate URL here.
8786   * @param array $options Additional options that can be specified for page setup.
8787   *      pagelayout - This option can be used to set a specific pagelyaout, admin is default.
8788   */
8789  function admin_externalpage_setup($section, $extrabutton = '', array $extraurlparams = null, $actualurl = '', array $options = array()) {
8790      global $CFG, $PAGE, $USER, $SITE, $OUTPUT;
8791  
8792      $PAGE->set_context(null); // hack - set context to something, by default to system context
8793  
8794      $site = get_site();
8795      require_login(null, false);
8796  
8797      if (!empty($options['pagelayout'])) {
8798          // A specific page layout has been requested.
8799          $PAGE->set_pagelayout($options['pagelayout']);
8800      } else if ($section === 'upgradesettings') {
8801          $PAGE->set_pagelayout('maintenance');
8802      } else {
8803          $PAGE->set_pagelayout('admin');
8804      }
8805  
8806      $adminroot = admin_get_root(false, false); // settings not required for external pages
8807      $extpage = $adminroot->locate($section, true);
8808  
8809      $hassiteconfig = has_capability('moodle/site:config', context_system::instance());
8810      if (empty($extpage) or !($extpage instanceof admin_externalpage)) {
8811          // The requested section isn't in the admin tree
8812          // It could be because the user has inadequate capapbilities or because the section doesn't exist
8813          if (!$hassiteconfig) {
8814              // The requested section could depend on a different capability
8815              // but most likely the user has inadequate capabilities
8816              print_error('accessdenied', 'admin');
8817          } else {
8818              print_error('sectionerror', 'admin', "$CFG->wwwroot/$CFG->admin/");
8819          }
8820      }
8821  
8822      // this eliminates our need to authenticate on the actual pages
8823      if (!$extpage->check_access()) {
8824          print_error('accessdenied', 'admin');
8825          die;
8826      }
8827  
8828      navigation_node::require_admin_tree();
8829  
8830      // $PAGE->set_extra_button($extrabutton); TODO
8831  
8832      if (!$actualurl) {
8833          $actualurl = $extpage->url;
8834      }
8835  
8836      $PAGE->set_url($actualurl, $extraurlparams);
8837      if (strpos($PAGE->pagetype, 'admin-') !== 0) {
8838          $PAGE->set_pagetype('admin-' . $PAGE->pagetype);
8839      }
8840  
8841      if (empty($SITE->fullname) || empty($SITE->shortname)) {
8842          // During initial install.
8843          $strinstallation = get_string('installation', 'install');
8844          $strsettings = get_string('settings');
8845          $PAGE->navbar->add($strsettings);
8846          $PAGE->set_title($strinstallation);
8847          $PAGE->set_heading($strinstallation);
8848          $PAGE->set_cacheable(false);
8849          return;
8850      }
8851  
8852      // Locate the current item on the navigation and make it active when found.
8853      $path = $extpage->path;
8854      $node = $PAGE->settingsnav;
8855      while ($node && count($path) > 0) {
8856          $node = $node->get(array_pop($path));
8857      }
8858      if ($node) {
8859          $node->make_active();
8860      }
8861  
8862      // Normal case.
8863      $adminediting = optional_param('adminedit', -1, PARAM_BOOL);
8864      if ($PAGE->user_allowed_editing() && $adminediting != -1) {
8865          $USER->editing = $adminediting;
8866      }
8867  
8868      $visiblepathtosection = array_reverse($extpage->visiblepath);
8869  
8870      if ($PAGE->user_allowed_editing() && !$PAGE->theme->haseditswitch) {
8871          if ($PAGE->user_is_editing()) {
8872              $caption = get_string('blockseditoff');
8873              $url = new moodle_url($PAGE->url, array('adminedit'=>'0', 'sesskey'=>sesskey()));
8874          } else {
8875              $caption = get_string('blocksediton');
8876              $url = new moodle_url($PAGE->url, array('adminedit'=>'1', 'sesskey'=>sesskey()));
8877          }
8878          $PAGE->set_button($OUTPUT->single_button($url, $caption, 'get'));
8879      }
8880  
8881      $PAGE->set_title("$SITE->shortname: " . implode(": ", $visiblepathtosection));
8882      $PAGE->set_heading($SITE->fullname);
8883  
8884      if ($hassiteconfig) {
8885          $PAGE->add_header_action($OUTPUT->render_from_template('core_admin/header_search_input', [
8886              'action' => new moodle_url('/admin/search.php'),
8887              'query' => $PAGE->url->get_param('query'),
8888          ]));
8889      }
8890  
8891      // prevent caching in nav block
8892      $PAGE->navigation->clear_cache();
8893  }
8894  
8895  /**
8896   * Returns the reference to admin tree root
8897   *
8898   * @return object admin_root object
8899   */
8900  function admin_get_root($reload=false, $requirefulltree=true) {
8901      global $CFG, $DB, $OUTPUT, $ADMIN;
8902  
8903      if (is_null($ADMIN)) {
8904      // create the admin tree!
8905          $ADMIN = new admin_root($requirefulltree);
8906      }
8907  
8908      if ($reload or ($requirefulltree and !$ADMIN->fulltree)) {
8909          $ADMIN->purge_children($requirefulltree);
8910      }
8911  
8912      if (!$ADMIN->loaded) {
8913      // we process this file first to create categories first and in correct order
8914          require($CFG->dirroot.'/'.$CFG->admin.'/settings/top.php');
8915  
8916          // now we process all other files in admin/settings to build the admin tree
8917          foreach (glob($CFG->dirroot.'/'.$CFG->admin.'/settings/*.php') as $file) {
8918              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/top.php') {
8919                  continue;
8920              }
8921              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php') {
8922              // plugins are loaded last - they may insert pages anywhere
8923                  continue;
8924              }
8925              require($file);
8926          }
8927          require($CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php');
8928  
8929          $ADMIN->loaded = true;
8930      }
8931  
8932      return $ADMIN;
8933  }
8934  
8935  /// settings utility functions
8936  
8937  /**
8938   * This function applies default settings.
8939   * Because setting the defaults of some settings can enable other settings,
8940   * this function is called recursively until no more new settings are found.
8941   *
8942   * @param object $node, NULL means complete tree, null by default
8943   * @param bool $unconditional if true overrides all values with defaults, true by default
8944   * @param array $admindefaultsettings default admin settings to apply. Used recursively
8945   * @param array $settingsoutput The names and values of the changed settings. Used recursively
8946   * @return array $settingsoutput The names and values of the changed settings
8947   */
8948  function admin_apply_default_settings($node=null, $unconditional=true, $admindefaultsettings=array(), $settingsoutput=array()) {
8949      $counter = 0;
8950  
8951      if (is_null($node)) {
8952          core_plugin_manager::reset_caches();
8953          $node = admin_get_root(true, true);
8954          $counter = count($settingsoutput);
8955      }
8956  
8957      if ($node instanceof admin_category) {
8958          $entries = array_keys($node->children);
8959          foreach ($entries as $entry) {
8960              $settingsoutput = admin_apply_default_settings(
8961                      $node->children[$entry], $unconditional, $admindefaultsettings, $settingsoutput
8962                      );
8963          }
8964  
8965      } else if ($node instanceof admin_settingpage) {
8966          foreach ($node->settings as $setting) {
8967              if (!$unconditional && !is_null($setting->get_setting())) {
8968                  // Do not override existing defaults.
8969                  continue;
8970              }
8971              $defaultsetting = $setting->get_defaultsetting();
8972              if (is_null($defaultsetting)) {
8973                  // No value yet - default maybe applied after admin user creation or in upgradesettings.
8974                  continue;
8975              }
8976  
8977              $settingname = $node->name . '_' . $setting->name; // Get a unique name for the setting.
8978  
8979              if (!array_key_exists($settingname, $admindefaultsettings)) {  // Only update a setting if not already processed.
8980                  $admindefaultsettings[$settingname] = $settingname;
8981                  $settingsoutput[$settingname] = $defaultsetting;
8982  
8983                  // Set the default for this setting.
8984                  $setting->write_setting($defaultsetting);
8985                  $setting->write_setting_flags(null);
8986              } else {
8987                  unset($admindefaultsettings[$settingname]); // Remove processed settings.
8988              }
8989          }
8990      }
8991  
8992      // Call this function recursively until all settings are processed.
8993      if (($node instanceof admin_root) && ($counter != count($settingsoutput))) {
8994          $settingsoutput = admin_apply_default_settings(null, $unconditional, $admindefaultsettings, $settingsoutput);
8995      }
8996      // Just in case somebody modifies the list of active plugins directly.
8997      core_plugin_manager::reset_caches();
8998  
8999      return $settingsoutput;
9000  }
9001  
9002  /**
9003   * Store changed settings, this function updates the errors variable in $ADMIN
9004   *
9005   * @param object $formdata from form
9006   * @return int number of changed settings
9007   */
9008  function admin_write_settings($formdata) {
9009      global $CFG, $SITE, $DB;
9010  
9011      $olddbsessions = !empty($CFG->dbsessions);
9012      $formdata = (array)$formdata;
9013  
9014      $data = array();
9015      foreach ($formdata as $fullname=>$value) {
9016          if (strpos($fullname, 's_') !== 0) {
9017              continue; // not a config value
9018          }
9019          $data[$fullname] = $value;
9020      }
9021  
9022      $adminroot = admin_get_root();
9023      $settings = admin_find_write_settings($adminroot, $data);
9024  
9025      $count = 0;
9026      foreach ($settings as $fullname=>$setting) {
9027          /** @var $setting admin_setting */
9028          $original = $setting->get_setting();
9029          $error = $setting->write_setting($data[$fullname]);
9030          if ($error !== '') {
9031              $adminroot->errors[$fullname] = new stdClass();
9032              $adminroot->errors[$fullname]->data  = $data[$fullname];
9033              $adminroot->errors[$fullname]->id    = $setting->get_id();
9034              $adminroot->errors[$fullname]->error = $error;
9035          } else {
9036              $setting->write_setting_flags($data);
9037          }
9038          if ($setting->post_write_settings($original)) {
9039              $count++;
9040          }
9041      }
9042  
9043      if ($olddbsessions != !empty($CFG->dbsessions)) {
9044          require_logout();
9045      }
9046  
9047      // Now update $SITE - just update the fields, in case other people have a
9048      // a reference to it (e.g. $PAGE, $COURSE).
9049      $newsite = $DB->get_record('course', array('id'=>$SITE->id));
9050      foreach (get_object_vars($newsite) as $field => $value) {
9051          $SITE->$field = $value;
9052      }
9053  
9054      // now reload all settings - some of them might depend on the changed
9055      admin_get_root(true);
9056      return $count;
9057  }
9058  
9059  /**
9060   * Internal recursive function - finds all settings from submitted form
9061   *
9062   * @param object $node Instance of admin_category, or admin_settingpage
9063   * @param array $data
9064   * @return array
9065   */
9066  function admin_find_write_settings($node, $data) {
9067      $return = array();
9068  
9069      if (empty($data)) {
9070          return $return;
9071      }
9072  
9073      if ($node instanceof admin_category) {
9074          if ($node->check_access()) {
9075              $entries = array_keys($node->children);
9076              foreach ($entries as $entry) {
9077                  $return = array_merge($return, admin_find_write_settings($node->children[$entry], $data));
9078              }
9079          }
9080  
9081      } else if ($node instanceof admin_settingpage) {
9082          if ($node->check_access()) {
9083              foreach ($node->settings as $setting) {
9084                  $fullname = $setting->get_full_name();
9085                  if (array_key_exists($fullname, $data)) {
9086                      $return[$fullname] = $setting;
9087                  }
9088              }
9089          }
9090  
9091      }
9092  
9093      return $return;
9094  }
9095  
9096  /**
9097   * Internal function - prints the search results
9098   *
9099   * @param string $query String to search for
9100   * @return string empty or XHTML
9101   */
9102  function admin_search_settings_html($query) {
9103      global $CFG, $OUTPUT, $PAGE;
9104  
9105      if (core_text::strlen($query) < 2) {
9106          return '';
9107      }
9108      $query = core_text::strtolower($query);
9109  
9110      $adminroot = admin_get_root();
9111      $findings = $adminroot->search($query);
9112      $savebutton = false;
9113  
9114      $tpldata = (object) [
9115          'actionurl' => $PAGE->url->out(false),
9116          'results' => [],
9117          'sesskey' => sesskey(),
9118      ];
9119  
9120      foreach ($findings as $found) {
9121          $page     = $found->page;
9122          $settings = $found->settings;
9123          if ($page->is_hidden()) {
9124          // hidden pages are not displayed in search results
9125              continue;
9126          }
9127  
9128          $heading = highlight($query, $page->visiblename);
9129          $headingurl = null;
9130          if ($page instanceof admin_externalpage) {
9131              $headingurl = new moodle_url($page->url);
9132          } else if ($page instanceof admin_settingpage) {
9133              $headingurl = new moodle_url('/admin/settings.php', ['section' => $page->name]);
9134          } else {
9135              continue;
9136          }
9137  
9138          // Locate the page in the admin root and populate its visiblepath attribute.
9139          $path = array();
9140          $located = $adminroot->locate($page->name, true);
9141          if ($located) {
9142              foreach ($located->visiblepath as $pathitem) {
9143                  array_unshift($path, (string) $pathitem);
9144              }
9145          }
9146  
9147          $sectionsettings = [];
9148          if (!empty($settings)) {
9149              foreach ($settings as $setting) {
9150                  if (empty($setting->nosave)) {
9151                      $savebutton = true;
9152                  }
9153                  $fullname = $setting->get_full_name();
9154                  if (array_key_exists($fullname, $adminroot->errors)) {
9155                      $data = $adminroot->errors[$fullname]->data;
9156                  } else {
9157                      $data = $setting->get_setting();
9158                  // do not use defaults if settings not available - upgradesettings handles the defaults!
9159                  }
9160                  $sectionsettings[] = $setting->output_html($data, $query);
9161              }
9162          }
9163  
9164          $tpldata->results[] = (object) [
9165              'title' => $heading,
9166              'path' => $path,
9167              'url' => $headingurl->out(false),
9168              'settings' => $sectionsettings
9169          ];
9170      }
9171  
9172      $tpldata->showsave = $savebutton;
9173      $tpldata->hasresults = !empty($tpldata->results);
9174  
9175      return $OUTPUT->render_from_template('core_admin/settings_search_results', $tpldata);
9176  }
9177  
9178  /**
9179   * Internal function - returns arrays of html pages with uninitialised settings
9180   *
9181   * @param object $node Instance of admin_category or admin_settingpage
9182   * @return array
9183   */
9184  function admin_output_new_settings_by_page($node) {
9185      global $OUTPUT;
9186      $return = array();
9187  
9188      if ($node instanceof admin_category) {
9189          $entries = array_keys($node->children);
9190          foreach ($entries as $entry) {
9191              $return += admin_output_new_settings_by_page($node->children[$entry]);
9192          }
9193  
9194      } else if ($node instanceof admin_settingpage) {
9195              $newsettings = array();
9196              foreach ($node->settings as $setting) {
9197                  if (is_null($setting->get_setting())) {
9198                      $newsettings[] = $setting;
9199                  }
9200              }
9201              if (count($newsettings) > 0) {
9202                  $adminroot = admin_get_root();
9203                  $page = $OUTPUT->heading(get_string('upgradesettings','admin').' - '.$node->visiblename, 2, 'main');
9204                  $page .= '<fieldset class="adminsettings">'."\n";
9205                  foreach ($newsettings as $setting) {
9206                      $fullname = $setting->get_full_name();
9207                      if (array_key_exists($fullname, $adminroot->errors)) {
9208                          $data = $adminroot->errors[$fullname]->data;
9209                      } else {
9210                          $data = $setting->get_setting();
9211                          if (is_null($data)) {
9212                              $data = $setting->get_defaultsetting();
9213                          }
9214                      }
9215                      $page .= '<div class="clearer"><!-- --></div>'."\n";
9216                      $page .= $setting->output_html($data);
9217                  }
9218                  $page .= '</fieldset>';
9219                  $return[$node->name] = $page;
9220              }
9221          }
9222  
9223      return $return;
9224  }
9225  
9226  /**
9227   * Format admin settings
9228   *
9229   * @param object $setting
9230   * @param string $title label element
9231   * @param string $form form fragment, html code - not highlighted automatically
9232   * @param string $description
9233   * @param mixed $label link label to id, true by default or string being the label to connect it to
9234   * @param string $warning warning text
9235   * @param sting $defaultinfo defaults info, null means nothing, '' is converted to "Empty" string, defaults to null
9236   * @param string $query search query to be highlighted
9237   * @return string XHTML
9238   */
9239  function format_admin_setting($setting, $title='', $form='', $description='', $label=true, $warning='', $defaultinfo=NULL, $query='') {
9240      global $CFG, $OUTPUT;
9241  
9242      $context = (object) [
9243          'name' => empty($setting->plugin) ? $setting->name : "$setting->plugin | $setting->name",
9244          'fullname' => $setting->get_full_name(),
9245      ];
9246  
9247      // Sometimes the id is not id_s_name, but id_s_name_m or something, and this does not validate.
9248      if ($label === true) {
9249          $context->labelfor = $setting->get_id();
9250      } else if ($label === false) {
9251          $context->labelfor = '';
9252      } else {
9253          $context->labelfor = $label;
9254      }
9255  
9256      $form .= $setting->output_setting_flags();
9257  
9258      $context->warning = $warning;
9259      $context->override = '';
9260      if (empty($setting->plugin)) {
9261          if (array_key_exists($setting->name, $CFG->config_php_settings)) {
9262              $context->override = get_string('configoverride', 'admin');
9263          }
9264      } else {
9265          if (array_key_exists($setting->plugin, $CFG->forced_plugin_settings) and array_key_exists($setting->name, $CFG->forced_plugin_settings[$setting->plugin])) {
9266              $context->override = get_string('configoverride', 'admin');
9267          }
9268      }
9269  
9270      $defaults = array();
9271      if (!is_null($defaultinfo)) {
9272          if ($defaultinfo === '') {
9273              $defaultinfo = get_string('emptysettingvalue', 'admin');
9274          }
9275          $defaults[] = $defaultinfo;
9276      }
9277  
9278      $context->default = null;
9279      $setting->get_setting_flag_defaults($defaults);
9280      if (!empty($defaults)) {
9281          $defaultinfo = implode(', ', $defaults);
9282          $defaultinfo = highlight($query, nl2br(s($defaultinfo)));
9283          $context->default = get_string('defaultsettinginfo', 'admin', $defaultinfo);
9284      }
9285  
9286  
9287      $context->error = '';
9288      $adminroot = admin_get_root();
9289      if (array_key_exists($context->fullname, $adminroot->errors)) {
9290          $context->error = $adminroot->errors[$context->fullname]->error;
9291      }
9292  
9293      if ($dependenton = $setting->get_dependent_on()) {
9294          $context->dependenton = get_string('settingdependenton', 'admin', implode(', ', $dependenton));
9295      }
9296  
9297      $context->id = 'admin-' . $setting->name;
9298      $context->title = highlightfast($query, $title);
9299      $context->name = highlightfast($query, $context->name);
9300      $context->description = highlight($query, markdown_to_html($description));
9301      $context->element = $form;
9302      $context->forceltr = $setting->get_force_ltr();
9303      $context->customcontrol = $setting->has_custom_form_control();
9304  
9305      return $OUTPUT->render_from_template('core_admin/setting', $context);
9306  }
9307  
9308  /**
9309   * Based on find_new_settings{@link ()}  in upgradesettings.php
9310   * Looks to find any admin settings that have not been initialized. Returns 1 if it finds any.
9311   *
9312   * @param object $node Instance of admin_category, or admin_settingpage
9313   * @return boolean true if any settings haven't been initialised, false if they all have
9314   */
9315  function any_new_admin_settings($node) {
9316  
9317      if ($node instanceof admin_category) {
9318          $entries = array_keys($node->children);
9319          foreach ($entries as $entry) {
9320              if (any_new_admin_settings($node->children[$entry])) {
9321                  return true;
9322              }
9323          }
9324  
9325      } else if ($node instanceof admin_settingpage) {
9326              foreach ($node->settings as $setting) {
9327                  if ($setting->get_setting() === NULL) {
9328                      return true;
9329                  }
9330              }
9331          }
9332  
9333      return false;
9334  }
9335  
9336  /**
9337   * Given a table and optionally a column name should replaces be done?
9338   *
9339   * @param string $table name
9340   * @param string $column name
9341   * @return bool success or fail
9342   */
9343  function db_should_replace($table, $column = '', $additionalskiptables = ''): bool {
9344  
9345      // TODO: this is horrible hack, we should have a hook and each plugin should be responsible for proper replacing...
9346      $skiptables = ['config', 'config_plugins', 'filter_config', 'sessions',
9347          'events_queue', 'repository_instance_config', 'block_instances', 'files'];
9348  
9349      // Additional skip tables.
9350      if (!empty($additionalskiptables)) {
9351          $skiptables = array_merge($skiptables, explode(',', str_replace(' ', '',  $additionalskiptables)));
9352      }
9353  
9354      // Don't process these.
9355      if (in_array($table, $skiptables)) {
9356          return false;
9357      }
9358  
9359      // To be safe never replace inside a table that looks related to logging.
9360      if (preg_match('/(^|_)logs?($|_)/', $table)) {
9361          return false;
9362      }
9363  
9364      // Do column based exclusions.
9365      if (!empty($column)) {
9366          // Don't touch anything that looks like a hash.
9367          if (preg_match('/hash$/', $column)) {
9368              return false;
9369          }
9370      }
9371  
9372      return true;
9373  }
9374  
9375  /**
9376   * Moved from admin/replace.php so that we can use this in cron
9377   *
9378   * @param string $search string to look for
9379   * @param string $replace string to replace
9380   * @return bool success or fail
9381   */
9382  function db_replace($search, $replace, $additionalskiptables = '') {
9383      global $DB, $CFG, $OUTPUT;
9384  
9385      // Turn off time limits, sometimes upgrades can be slow.
9386      core_php_time_limit::raise();
9387  
9388      if (!$tables = $DB->get_tables() ) {    // No tables yet at all.
9389          return false;
9390      }
9391      foreach ($tables as $table) {
9392  
9393          if (!db_should_replace($table, '', $additionalskiptables)) {
9394              continue;
9395          }
9396  
9397          if ($columns = $DB->get_columns($table)) {
9398              $DB->set_debug(true);
9399              foreach ($columns as $column) {
9400                  if (!db_should_replace($table, $column->name)) {
9401                      continue;
9402                  }
9403                  $DB->replace_all_text($table, $column, $search, $replace);
9404              }
9405              $DB->set_debug(false);
9406          }
9407      }
9408  
9409      // delete modinfo caches
9410      rebuild_course_cache(0, true);
9411  
9412      // TODO: we should ask all plugins to do the search&replace, for now let's do only blocks...
9413      $blocks = core_component::get_plugin_list('block');
9414      foreach ($blocks as $blockname=>$fullblock) {
9415          if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
9416              continue;
9417          }
9418  
9419          if (!is_readable($fullblock.'/lib.php')) {
9420              continue;
9421          }
9422  
9423          $function = 'block_'.$blockname.'_global_db_replace';
9424          include_once($fullblock.'/lib.php');
9425          if (!function_exists($function)) {
9426              continue;
9427          }
9428  
9429          echo $OUTPUT->notification("Replacing in $blockname blocks...", 'notifysuccess');
9430          $function($search, $replace);
9431          echo $OUTPUT->notification("...finished", 'notifysuccess');
9432      }
9433  
9434      // Trigger an event.
9435      $eventargs = [
9436          'context' => context_system::instance(),
9437          'other' => [
9438              'search' => $search,
9439              'replace' => $replace
9440          ]
9441      ];
9442      $event = \core\event\database_text_field_content_replaced::create($eventargs);
9443      $event->trigger();
9444  
9445      purge_all_caches();
9446  
9447      return true;
9448  }
9449  
9450  /**
9451   * Manage repository settings
9452   *
9453   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
9454   */
9455  class admin_setting_managerepository extends admin_setting {
9456  /** @var string */
9457      private $baseurl;
9458  
9459      /**
9460       * calls parent::__construct with specific arguments
9461       */
9462      public function __construct() {
9463          global $CFG;
9464          parent::__construct('managerepository', get_string('manage', 'repository'), '', '');
9465          $this->baseurl = $CFG->wwwroot . '/' . $CFG->admin . '/repository.php?sesskey=' . sesskey();
9466      }
9467  
9468      /**
9469       * Always returns true, does nothing
9470       *
9471       * @return true
9472       */
9473      public function get_setting() {
9474          return true;
9475      }
9476  
9477      /**
9478       * Always returns true does nothing
9479       *
9480       * @return true
9481       */
9482      public function get_defaultsetting() {
9483          return true;
9484      }
9485  
9486      /**
9487       * Always returns s_managerepository
9488       *
9489       * @return string Always return 's_managerepository'
9490       */
9491      public function get_full_name() {
9492          return 's_managerepository';
9493      }
9494  
9495      /**
9496       * Always returns '' doesn't do anything
9497       */
9498      public function write_setting($data) {
9499          $url = $this->baseurl . '&amp;new=' . $data;
9500          return '';
9501      // TODO
9502      // Should not use redirect and exit here
9503      // Find a better way to do this.
9504      // redirect($url);
9505      // exit;
9506      }
9507  
9508      /**
9509       * Searches repository plugins for one that matches $query
9510       *
9511       * @param string $query The string to search for
9512       * @return bool true if found, false if not
9513       */
9514      public function is_related($query) {
9515          if (parent::is_related($query)) {
9516              return true;
9517          }
9518  
9519          $repositories= core_component::get_plugin_list('repository');
9520          foreach ($repositories as $p => $dir) {
9521              if (strpos($p, $query) !== false) {
9522                  return true;
9523              }
9524          }
9525          foreach (repository::get_types() as $instance) {
9526              $title = $instance->get_typename();
9527              if (strpos(core_text::strtolower($title), $query) !== false) {
9528                  return true;
9529              }
9530          }
9531          return false;
9532      }
9533  
9534      /**
9535       * Helper function that generates a moodle_url object
9536       * relevant to the repository
9537       */
9538  
9539      function repository_action_url($repository) {
9540          return new moodle_url($this->baseurl, array('sesskey'=>sesskey(), 'repos'=>$repository));
9541      }
9542  
9543      /**
9544       * Builds XHTML to display the control
9545       *
9546       * @param string $data Unused
9547       * @param string $query
9548       * @return string XHTML
9549       */
9550      public function output_html($data, $query='') {
9551          global $CFG, $USER, $OUTPUT;
9552  
9553          // Get strings that are used
9554          $strshow = get_string('on', 'repository');
9555          $strhide = get_string('off', 'repository');
9556          $strdelete = get_string('disabled', 'repository');
9557  
9558          $actionchoicesforexisting = array(
9559              'show' => $strshow,
9560              'hide' => $strhide,
9561              'delete' => $strdelete
9562          );
9563  
9564          $actionchoicesfornew = array(
9565              'newon' => $strshow,
9566              'newoff' => $strhide,
9567              'delete' => $strdelete
9568          );
9569  
9570          $return = '';
9571          $return .= $OUTPUT->box_start('generalbox');
9572  
9573          // Set strings that are used multiple times
9574          $settingsstr = get_string('settings');
9575          $disablestr = get_string('disable');
9576  
9577          // Table to list plug-ins
9578          $table = new html_table();
9579          $table->head = array(get_string('name'), get_string('isactive', 'repository'), get_string('order'), $settingsstr);
9580          $table->align = array('left', 'center', 'center', 'center', 'center');
9581          $table->data = array();
9582  
9583          // Get list of used plug-ins
9584          $repositorytypes = repository::get_types();
9585          if (!empty($repositorytypes)) {
9586              // Array to store plugins being used
9587              $alreadyplugins = array();
9588              $totalrepositorytypes = count($repositorytypes);
9589              $updowncount = 1;
9590              foreach ($repositorytypes as $i) {
9591                  $settings = '';
9592                  $typename = $i->get_typename();
9593                  // Display edit link only if you can config the type or if it has multiple instances (e.g. has instance config)
9594                  $typeoptionnames = repository::static_function($typename, 'get_type_option_names');
9595                  $instanceoptionnames = repository::static_function($typename, 'get_instance_option_names');
9596  
9597                  if (!empty($typeoptionnames) || !empty($instanceoptionnames)) {
9598                      // Calculate number of instances in order to display them for the Moodle administrator
9599                      if (!empty($instanceoptionnames)) {
9600                          $params = array();
9601                          $params['context'] = array(context_system::instance());
9602                          $params['onlyvisible'] = false;
9603                          $params['type'] = $typename;
9604                          $admininstancenumber = count(repository::static_function($typename, 'get_instances', $params));
9605                          // site instances
9606                          $admininstancenumbertext = get_string('instancesforsite', 'repository', $admininstancenumber);
9607                          $params['context'] = array();
9608                          $instances = repository::static_function($typename, 'get_instances', $params);
9609                          $courseinstances = array();
9610                          $userinstances = array();
9611  
9612                          foreach ($instances as $instance) {
9613                              $repocontext = context::instance_by_id($instance->instance->contextid);
9614                              if ($repocontext->contextlevel == CONTEXT_COURSE) {
9615                                  $courseinstances[] = $instance;
9616                              } else if ($repocontext->contextlevel == CONTEXT_USER) {
9617                                  $userinstances[] = $instance;
9618                              }
9619                          }
9620                          // course instances
9621                          $instancenumber = count($courseinstances);
9622                          $courseinstancenumbertext = get_string('instancesforcourses', 'repository', $instancenumber);
9623  
9624                          // user private instances
9625                          $instancenumber =  count($userinstances);
9626                          $userinstancenumbertext = get_string('instancesforusers', 'repository', $instancenumber);
9627                      } else {
9628                          $admininstancenumbertext = "";
9629                          $courseinstancenumbertext = "";
9630                          $userinstancenumbertext = "";
9631                      }
9632  
9633                      $settings .= '<a href="' . $this->baseurl . '&amp;action=edit&amp;repos=' . $typename . '">' . $settingsstr .'</a>';
9634  
9635                      $settings .= $OUTPUT->container_start('mdl-left');
9636                      $settings .= '<br/>';
9637                      $settings .= $admininstancenumbertext;
9638                      $settings .= '<br/>';
9639                      $settings .= $courseinstancenumbertext;
9640                      $settings .= '<br/>';
9641                      $settings .= $userinstancenumbertext;
9642                      $settings .= $OUTPUT->container_end();
9643                  }
9644                  // Get the current visibility
9645                  if ($i->get_visible()) {
9646                      $currentaction = 'show';
9647                  } else {
9648                      $currentaction = 'hide';
9649                  }
9650  
9651                  $select = new single_select($this->repository_action_url($typename, 'repos'), 'action', $actionchoicesforexisting, $currentaction, null, 'applyto' . basename($typename));
9652  
9653                  // Display up/down link
9654                  $updown = '';
9655                  // Should be done with CSS instead.
9656                  $spacer = $OUTPUT->spacer(array('height' => 15, 'width' => 15, 'class' => 'smallicon'));
9657  
9658                  if ($updowncount > 1) {
9659                      $updown .= "<a href=\"$this->baseurl&amp;action=moveup&amp;repos=".$typename."\">";
9660                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
9661                  }
9662                  else {
9663                      $updown .= $spacer;
9664                  }
9665                  if ($updowncount < $totalrepositorytypes) {
9666                      $updown .= "<a href=\"$this->baseurl&amp;action=movedown&amp;repos=".$typename."\">";
9667                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
9668                  }
9669                  else {
9670                      $updown .= $spacer;
9671                  }
9672  
9673                  $updowncount++;
9674  
9675                  $table->data[] = array($i->get_readablename(), $OUTPUT->render($select), $updown, $settings);
9676  
9677                  if (!in_array($typename, $alreadyplugins)) {
9678                      $alreadyplugins[] = $typename;
9679                  }
9680              }
9681          }
9682  
9683          // Get all the plugins that exist on disk
9684          $plugins = core_component::get_plugin_list('repository');
9685          if (!empty($plugins)) {
9686              foreach ($plugins as $plugin => $dir) {
9687                  // Check that it has not already been listed
9688                  if (!in_array($plugin, $alreadyplugins)) {
9689                      $select = new single_select($this->repository_action_url($plugin, 'repos'), 'action', $actionchoicesfornew, 'delete', null, 'applyto' . basename($plugin));
9690                      $table->data[] = array(get_string('pluginname', 'repository_'.$plugin), $OUTPUT->render($select), '', '');
9691                  }
9692              }
9693          }
9694  
9695          $return .= html_writer::table($table);
9696          $return .= $OUTPUT->box_end();
9697          return highlight($query, $return);
9698      }
9699  }
9700  
9701  /**
9702   * Special checkbox for enable mobile web service
9703   * If enable then we store the service id of the mobile service into config table
9704   * If disable then we unstore the service id from the config table
9705   */
9706  class admin_setting_enablemobileservice extends admin_setting_configcheckbox {
9707  
9708      /** @var boolean True means that the capability 'webservice/rest:use' is set for authenticated user role */
9709      private $restuse;
9710  
9711      /**
9712       * Return true if Authenticated user role has the capability 'webservice/rest:use', otherwise false.
9713       *
9714       * @return boolean
9715       */
9716      private function is_protocol_cap_allowed() {
9717          global $DB, $CFG;
9718  
9719          // If the $this->restuse variable is not set, it needs to be set.
9720          if (empty($this->restuse) and $this->restuse!==false) {
9721              $params = array();
9722              $params['permission'] = CAP_ALLOW;
9723              $params['roleid'] = $CFG->defaultuserroleid;
9724              $params['capability'] = 'webservice/rest:use';
9725              $this->restuse = $DB->record_exists('role_capabilities', $params);
9726          }
9727  
9728          return $this->restuse;
9729      }
9730  
9731      /**
9732       * Set the 'webservice/rest:use' to the Authenticated user role (allow or not)
9733       * @param type $status true to allow, false to not set
9734       */
9735      private function set_protocol_cap($status) {
9736          global $CFG;
9737          if ($status and !$this->is_protocol_cap_allowed()) {
9738              //need to allow the cap
9739              $permission = CAP_ALLOW;
9740              $assign = true;
9741          } else if (!$status and $this->is_protocol_cap_allowed()){
9742              //need to disallow the cap
9743              $permission = CAP_INHERIT;
9744              $assign = true;
9745          }
9746          if (!empty($assign)) {
9747              $systemcontext = context_system::instance();
9748              assign_capability('webservice/rest:use', $permission, $CFG->defaultuserroleid, $systemcontext->id, true);
9749          }
9750      }
9751  
9752      /**
9753       * Builds XHTML to display the control.
9754       * The main purpose of this overloading is to display a warning when https
9755       * is not supported by the server
9756       * @param string $data Unused
9757       * @param string $query
9758       * @return string XHTML
9759       */
9760      public function output_html($data, $query='') {
9761          global $OUTPUT;
9762          $html = parent::output_html($data, $query);
9763  
9764          if ((string)$data === $this->yes) {
9765              $notifications = tool_mobile\api::get_potential_config_issues(); // Safe to call, plugin available if we reach here.
9766              foreach ($notifications as $notification) {
9767                  $message = get_string($notification[0], $notification[1]);
9768                  $html .= $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING);
9769              }
9770          }
9771  
9772          return $html;
9773      }
9774  
9775      /**
9776       * Retrieves the current setting using the objects name
9777       *
9778       * @return string
9779       */
9780      public function get_setting() {
9781          global $CFG;
9782  
9783          // First check if is not set.
9784          $result = $this->config_read($this->name);
9785          if (is_null($result)) {
9786              return null;
9787          }
9788  
9789          // For install cli script, $CFG->defaultuserroleid is not set so return 0
9790          // Or if web services aren't enabled this can't be,
9791          if (empty($CFG->defaultuserroleid) || empty($CFG->enablewebservices)) {
9792              return 0;
9793          }
9794  
9795          require_once($CFG->dirroot . '/webservice/lib.php');
9796          $webservicemanager = new webservice();
9797          $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9798          if ($mobileservice->enabled and $this->is_protocol_cap_allowed()) {
9799              return $result;
9800          } else {
9801              return 0;
9802          }
9803      }
9804  
9805      /**
9806       * Save the selected setting
9807       *
9808       * @param string $data The selected site
9809       * @return string empty string or error message
9810       */
9811      public function write_setting($data) {
9812          global $DB, $CFG;
9813  
9814          //for install cli script, $CFG->defaultuserroleid is not set so do nothing
9815          if (empty($CFG->defaultuserroleid)) {
9816              return '';
9817          }
9818  
9819          $servicename = MOODLE_OFFICIAL_MOBILE_SERVICE;
9820  
9821          require_once($CFG->dirroot . '/webservice/lib.php');
9822          $webservicemanager = new webservice();
9823  
9824          $updateprotocol = false;
9825          if ((string)$data === $this->yes) {
9826               //code run when enable mobile web service
9827               //enable web service systeme if necessary
9828               set_config('enablewebservices', true);
9829  
9830               //enable mobile service
9831               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9832               $mobileservice->enabled = 1;
9833               $webservicemanager->update_external_service($mobileservice);
9834  
9835               // Enable REST server.
9836               $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
9837  
9838               if (!in_array('rest', $activeprotocols)) {
9839                   $activeprotocols[] = 'rest';
9840                   $updateprotocol = true;
9841               }
9842  
9843               if ($updateprotocol) {
9844                   set_config('webserviceprotocols', implode(',', $activeprotocols));
9845               }
9846  
9847               // Allow rest:use capability for authenticated user.
9848               $this->set_protocol_cap(true);
9849           } else {
9850               // Disable the mobile service.
9851               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9852               $mobileservice->enabled = 0;
9853               $webservicemanager->update_external_service($mobileservice);
9854           }
9855  
9856          return (parent::write_setting($data));
9857      }
9858  }
9859  
9860  /**
9861   * Special class for management of external services
9862   *
9863   * @author Petr Skoda (skodak)
9864   */
9865  class admin_setting_manageexternalservices extends admin_setting {
9866      /**
9867       * Calls parent::__construct with specific arguments
9868       */
9869      public function __construct() {
9870          $this->nosave = true;
9871          parent::__construct('webservicesui', get_string('externalservices', 'webservice'), '', '');
9872      }
9873  
9874      /**
9875       * Always returns true, does nothing
9876       *
9877       * @return true
9878       */
9879      public function get_setting() {
9880          return true;
9881      }
9882  
9883      /**
9884       * Always returns true, does nothing
9885       *
9886       * @return true
9887       */
9888      public function get_defaultsetting() {
9889          return true;
9890      }
9891  
9892      /**
9893       * Always returns '', does not write anything
9894       *
9895       * @return string Always returns ''
9896       */
9897      public function write_setting($data) {
9898      // do not write any setting
9899          return '';
9900      }
9901  
9902      /**
9903       * Checks if $query is one of the available external services
9904       *
9905       * @param string $query The string to search for
9906       * @return bool Returns true if found, false if not
9907       */
9908      public function is_related($query) {
9909          global $DB;
9910  
9911          if (parent::is_related($query)) {
9912              return true;
9913          }
9914  
9915          $services = $DB->get_records('external_services', array(), 'id, name');
9916          foreach ($services as $service) {
9917              if (strpos(core_text::strtolower($service->name), $query) !== false) {
9918                  return true;
9919              }
9920          }
9921          return false;
9922      }
9923  
9924      /**
9925       * Builds the XHTML to display the control
9926       *
9927       * @param string $data Unused
9928       * @param string $query
9929       * @return string
9930       */
9931      public function output_html($data, $query='') {
9932          global $CFG, $OUTPUT, $DB;
9933  
9934          // display strings
9935          $stradministration = get_string('administration');
9936          $stredit = get_string('edit');
9937          $strservice = get_string('externalservice', 'webservice');
9938          $strdelete = get_string('delete');
9939          $strplugin = get_string('plugin', 'admin');
9940          $stradd = get_string('add');
9941          $strfunctions = get_string('functions', 'webservice');
9942          $strusers = get_string('users');
9943          $strserviceusers = get_string('serviceusers', 'webservice');
9944  
9945          $esurl = "$CFG->wwwroot/$CFG->admin/webservice/service.php";
9946          $efurl = "$CFG->wwwroot/$CFG->admin/webservice/service_functions.php";
9947          $euurl = "$CFG->wwwroot/$CFG->admin/webservice/service_users.php";
9948  
9949          // built in services
9950           $services = $DB->get_records_select('external_services', 'component IS NOT NULL', null, 'name');
9951           $return = "";
9952           if (!empty($services)) {
9953              $return .= $OUTPUT->heading(get_string('servicesbuiltin', 'webservice'), 3, 'main');
9954  
9955  
9956  
9957              $table = new html_table();
9958              $table->head  = array($strservice, $strplugin, $strfunctions, $strusers, $stredit);
9959              $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9960              $table->id = 'builtinservices';
9961              $table->attributes['class'] = 'admintable externalservices generaltable';
9962              $table->data  = array();
9963  
9964              // iterate through auth plugins and add to the display table
9965              foreach ($services as $service) {
9966                  $name = $service->name;
9967  
9968                  // hide/show link
9969                  if ($service->enabled) {
9970                      $displayname = "<span>$name</span>";
9971                  } else {
9972                      $displayname = "<span class=\"dimmed_text\">$name</span>";
9973                  }
9974  
9975                  $plugin = $service->component;
9976  
9977                  $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9978  
9979                  if ($service->restrictedusers) {
9980                      $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9981                  } else {
9982                      $users = get_string('allusers', 'webservice');
9983                  }
9984  
9985                  $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9986  
9987                  // add a row to the table
9988                  $table->data[] = array($displayname, $plugin, $functions, $users, $edit);
9989              }
9990              $return .= html_writer::table($table);
9991          }
9992  
9993          // Custom services
9994          $return .= $OUTPUT->heading(get_string('servicescustom', 'webservice'), 3, 'main');
9995          $services = $DB->get_records_select('external_services', 'component IS NULL', null, 'name');
9996  
9997          $table = new html_table();
9998          $table->head  = array($strservice, $strdelete, $strfunctions, $strusers, $stredit);
9999          $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
10000          $table->id = 'customservices';
10001          $table->attributes['class'] = 'admintable externalservices generaltable';
10002          $table->data  = array();
10003  
10004          // iterate through auth plugins and add to the display table
10005          foreach ($services as $service) {
10006              $name = $service->name;
10007  
10008              // hide/show link
10009              if ($service->enabled) {
10010                  $displayname = "<span>$name</span>";
10011              } else {
10012                  $displayname = "<span class=\"dimmed_text\">$name</span>";
10013              }
10014  
10015              // delete link
10016              $delete = "<a href=\"$esurl?action=delete&amp;sesskey=".sesskey()."&amp;id=$service->id\">$strdelete</a>";
10017  
10018              $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
10019  
10020              if ($service->restrictedusers) {
10021                  $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
10022              } else {
10023                  $users = get_string('allusers', 'webservice');
10024              }
10025  
10026              $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
10027  
10028              // add a row to the table
10029              $table->data[] = array($displayname, $delete, $functions, $users, $edit);
10030          }
10031          // add new custom service option
10032          $return .= html_writer::table($table);
10033  
10034          $return .= '<br />';
10035          // add a token to the table
10036          $return .= "<a href=\"$esurl?id=0\">$stradd</a>";
10037  
10038          return highlight($query, $return);
10039      }
10040  }
10041  
10042  /**
10043   * Special class for overview of external services
10044   *
10045   * @author Jerome Mouneyrac
10046   */
10047  class admin_setting_webservicesoverview extends admin_setting {
10048  
10049      /**
10050       * Calls parent::__construct with specific arguments
10051       */
10052      public function __construct() {
10053          $this->nosave = true;
10054          parent::__construct('webservicesoverviewui',
10055                          get_string('webservicesoverview', 'webservice'), '', '');
10056      }
10057  
10058      /**
10059       * Always returns true, does nothing
10060       *
10061       * @return true
10062       */
10063      public function get_setting() {
10064          return true;
10065      }
10066  
10067      /**
10068       * Always returns true, does nothing
10069       *
10070       * @return true
10071       */
10072      public function get_defaultsetting() {
10073          return true;
10074      }
10075  
10076      /**
10077       * Always returns '', does not write anything
10078       *
10079       * @return string Always returns ''
10080       */
10081      public function write_setting($data) {
10082          // do not write any setting
10083          return '';
10084      }
10085  
10086      /**
10087       * Builds the XHTML to display the control
10088       *
10089       * @param string $data Unused
10090       * @param string $query
10091       * @return string
10092       */
10093      public function output_html($data, $query='') {
10094          global $CFG, $OUTPUT;
10095  
10096          $return = "";
10097          $brtag = html_writer::empty_tag('br');
10098  
10099          /// One system controlling Moodle with Token
10100          $return .= $OUTPUT->heading(get_string('onesystemcontrolling', 'webservice'), 3, 'main');
10101          $table = new html_table();
10102          $table->head = array(get_string('step', 'webservice'), get_string('status'),
10103              get_string('description'));
10104          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
10105          $table->id = 'onesystemcontrol';
10106          $table->attributes['class'] = 'admintable wsoverview generaltable';
10107          $table->data = array();
10108  
10109          $return .= $brtag . get_string('onesystemcontrollingdescription', 'webservice')
10110                  . $brtag . $brtag;
10111  
10112          /// 1. Enable Web Services
10113          $row = array();
10114          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10115          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10116                          array('href' => $url));
10117          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10118          if ($CFG->enablewebservices) {
10119              $status = get_string('yes');
10120          }
10121          $row[1] = $status;
10122          $row[2] = get_string('enablewsdescription', 'webservice');
10123          $table->data[] = $row;
10124  
10125          /// 2. Enable protocols
10126          $row = array();
10127          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10128          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10129                          array('href' => $url));
10130          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10131          //retrieve activated protocol
10132          $active_protocols = empty($CFG->webserviceprotocols) ?
10133                  array() : explode(',', $CFG->webserviceprotocols);
10134          if (!empty($active_protocols)) {
10135              $status = "";
10136              foreach ($active_protocols as $protocol) {
10137                  $status .= $protocol . $brtag;
10138              }
10139          }
10140          $row[1] = $status;
10141          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10142          $table->data[] = $row;
10143  
10144          /// 3. Create user account
10145          $row = array();
10146          $url = new moodle_url("/user/editadvanced.php?id=-1");
10147          $row[0] = "3. " . html_writer::tag('a', get_string('createuser', 'webservice'),
10148                          array('href' => $url));
10149          $row[1] = "";
10150          $row[2] = get_string('createuserdescription', 'webservice');
10151          $table->data[] = $row;
10152  
10153          /// 4. Add capability to users
10154          $row = array();
10155          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10156          $row[0] = "4. " . html_writer::tag('a', get_string('checkusercapability', 'webservice'),
10157                          array('href' => $url));
10158          $row[1] = "";
10159          $row[2] = get_string('checkusercapabilitydescription', 'webservice');
10160          $table->data[] = $row;
10161  
10162          /// 5. Select a web service
10163          $row = array();
10164          $url = new moodle_url("/admin/settings.php?section=externalservices");
10165          $row[0] = "5. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10166                          array('href' => $url));
10167          $row[1] = "";
10168          $row[2] = get_string('createservicedescription', 'webservice');
10169          $table->data[] = $row;
10170  
10171          /// 6. Add functions
10172          $row = array();
10173          $url = new moodle_url("/admin/settings.php?section=externalservices");
10174          $row[0] = "6. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10175                          array('href' => $url));
10176          $row[1] = "";
10177          $row[2] = get_string('addfunctionsdescription', 'webservice');
10178          $table->data[] = $row;
10179  
10180          /// 7. Add the specific user
10181          $row = array();
10182          $url = new moodle_url("/admin/settings.php?section=externalservices");
10183          $row[0] = "7. " . html_writer::tag('a', get_string('selectspecificuser', 'webservice'),
10184                          array('href' => $url));
10185          $row[1] = "";
10186          $row[2] = get_string('selectspecificuserdescription', 'webservice');
10187          $table->data[] = $row;
10188  
10189          /// 8. Create token for the specific user
10190          $row = array();
10191          $url = new moodle_url('/admin/webservice/tokens.php', ['action' => 'create']);
10192          $row[0] = "8. " . html_writer::tag('a', get_string('createtokenforuser', 'webservice'),
10193                          array('href' => $url));
10194          $row[1] = "";
10195          $row[2] = get_string('createtokenforuserdescription', 'webservice');
10196          $table->data[] = $row;
10197  
10198          /// 9. Enable the documentation
10199          $row = array();
10200          $url = new moodle_url("/admin/search.php?query=enablewsdocumentation");
10201          $row[0] = "9. " . html_writer::tag('a', get_string('enabledocumentation', 'webservice'),
10202                          array('href' => $url));
10203          $status = '<span class="warning">' . get_string('no') . '</span>';
10204          if ($CFG->enablewsdocumentation) {
10205              $status = get_string('yes');
10206          }
10207          $row[1] = $status;
10208          $row[2] = get_string('enabledocumentationdescription', 'webservice');
10209          $table->data[] = $row;
10210  
10211          /// 10. Test the service
10212          $row = array();
10213          $url = new moodle_url("/admin/webservice/testclient.php");
10214          $row[0] = "10. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10215                          array('href' => $url));
10216          $row[1] = "";
10217          $row[2] = get_string('testwithtestclientdescription', 'webservice');
10218          $table->data[] = $row;
10219  
10220          $return .= html_writer::table($table);
10221  
10222          /// Users as clients with token
10223          $return .= $brtag . $brtag . $brtag;
10224          $return .= $OUTPUT->heading(get_string('userasclients', 'webservice'), 3, 'main');
10225          $table = new html_table();
10226          $table->head = array(get_string('step', 'webservice'), get_string('status'),
10227              get_string('description'));
10228          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
10229          $table->id = 'userasclients';
10230          $table->attributes['class'] = 'admintable wsoverview generaltable';
10231          $table->data = array();
10232  
10233          $return .= $brtag . get_string('userasclientsdescription', 'webservice') .
10234                  $brtag . $brtag;
10235  
10236          /// 1. Enable Web Services
10237          $row = array();
10238          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10239          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10240                          array('href' => $url));
10241          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10242          if ($CFG->enablewebservices) {
10243              $status = get_string('yes');
10244          }
10245          $row[1] = $status;
10246          $row[2] = get_string('enablewsdescription', 'webservice');
10247          $table->data[] = $row;
10248  
10249          /// 2. Enable protocols
10250          $row = array();
10251          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10252          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10253                          array('href' => $url));
10254          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10255          //retrieve activated protocol
10256          $active_protocols = empty($CFG->webserviceprotocols) ?
10257                  array() : explode(',', $CFG->webserviceprotocols);
10258          if (!empty($active_protocols)) {
10259              $status = "";
10260              foreach ($active_protocols as $protocol) {
10261                  $status .= $protocol . $brtag;
10262              }
10263          }
10264          $row[1] = $status;
10265          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10266          $table->data[] = $row;
10267  
10268  
10269          /// 3. Select a web service
10270          $row = array();
10271          $url = new moodle_url("/admin/settings.php?section=externalservices");
10272          $row[0] = "3. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10273                          array('href' => $url));
10274          $row[1] = "";
10275          $row[2] = get_string('createserviceforusersdescription', 'webservice');
10276          $table->data[] = $row;
10277  
10278          /// 4. Add functions
10279          $row = array();
10280          $url = new moodle_url("/admin/settings.php?section=externalservices");
10281          $row[0] = "4. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10282                          array('href' => $url));
10283          $row[1] = "";
10284          $row[2] = get_string('addfunctionsdescription', 'webservice');
10285          $table->data[] = $row;
10286  
10287          /// 5. Add capability to users
10288          $row = array();
10289          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10290          $row[0] = "5. " . html_writer::tag('a', get_string('addcapabilitytousers', 'webservice'),
10291                          array('href' => $url));
10292          $row[1] = "";
10293          $row[2] = get_string('addcapabilitytousersdescription', 'webservice');
10294          $table->data[] = $row;
10295  
10296          /// 6. Test the service
10297          $row = array();
10298          $url = new moodle_url("/admin/webservice/testclient.php");
10299          $row[0] = "6. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10300                          array('href' => $url));
10301          $row[1] = "";
10302          $row[2] = get_string('testauserwithtestclientdescription', 'webservice');
10303          $table->data[] = $row;
10304  
10305          $return .= html_writer::table($table);
10306  
10307          return highlight($query, $return);
10308      }
10309  
10310  }
10311  
10312  
10313  /**
10314   * Special class for web service protocol administration.
10315   *
10316   * @author Petr Skoda (skodak)
10317   */
10318  class admin_setting_managewebserviceprotocols extends admin_setting {
10319  
10320      /**
10321       * Calls parent::__construct with specific arguments
10322       */
10323      public function __construct() {
10324          $this->nosave = true;
10325          parent::__construct('webservicesui', get_string('manageprotocols', 'webservice'), '', '');
10326      }
10327  
10328      /**
10329       * Always returns true, does nothing
10330       *
10331       * @return true
10332       */
10333      public function get_setting() {
10334          return true;
10335      }
10336  
10337      /**
10338       * Always returns true, does nothing
10339       *
10340       * @return true
10341       */
10342      public function get_defaultsetting() {
10343          return true;
10344      }
10345  
10346      /**
10347       * Always returns '', does not write anything
10348       *
10349       * @return string Always returns ''
10350       */
10351      public function write_setting($data) {
10352      // do not write any setting
10353          return '';
10354      }
10355  
10356      /**
10357       * Checks if $query is one of the available webservices
10358       *
10359       * @param string $query The string to search for
10360       * @return bool Returns true if found, false if not
10361       */
10362      public function is_related($query) {
10363          if (parent::is_related($query)) {
10364              return true;
10365          }
10366  
10367          $protocols = core_component::get_plugin_list('webservice');
10368          foreach ($protocols as $protocol=>$location) {
10369              if (strpos($protocol, $query) !== false) {
10370                  return true;
10371              }
10372              $protocolstr = get_string('pluginname', 'webservice_'.$protocol);
10373              if (strpos(core_text::strtolower($protocolstr), $query) !== false) {
10374                  return true;
10375              }
10376          }
10377          return false;
10378      }
10379  
10380      /**
10381       * Builds the XHTML to display the control
10382       *
10383       * @param string $data Unused
10384       * @param string $query
10385       * @return string
10386       */
10387      public function output_html($data, $query='') {
10388          global $CFG, $OUTPUT;
10389  
10390          // display strings
10391          $stradministration = get_string('administration');
10392          $strsettings = get_string('settings');
10393          $stredit = get_string('edit');
10394          $strprotocol = get_string('protocol', 'webservice');
10395          $strenable = get_string('enable');
10396          $strdisable = get_string('disable');
10397          $strversion = get_string('version');
10398  
10399          $protocols_available = core_component::get_plugin_list('webservice');
10400          $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
10401          ksort($protocols_available);
10402  
10403          foreach ($activeprotocols as $key => $protocol) {
10404              if (empty($protocols_available[$protocol])) {
10405                  unset($activeprotocols[$key]);
10406              }
10407          }
10408  
10409          $return = $OUTPUT->heading(get_string('actwebserviceshhdr', 'webservice'), 3, 'main');
10410          if (in_array('xmlrpc', $activeprotocols)) {
10411              $notify = new \core\output\notification(get_string('xmlrpcwebserviceenabled', 'admin'),
10412                  \core\output\notification::NOTIFY_WARNING);
10413              $return .= $OUTPUT->render($notify);
10414          }
10415          $return .= $OUTPUT->box_start('generalbox webservicesui');
10416  
10417          $table = new html_table();
10418          $table->head  = array($strprotocol, $strversion, $strenable, $strsettings);
10419          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
10420          $table->id = 'webserviceprotocols';
10421          $table->attributes['class'] = 'admintable generaltable';
10422          $table->data  = array();
10423  
10424          // iterate through auth plugins and add to the display table
10425          $url = "$CFG->wwwroot/$CFG->admin/webservice/protocols.php?sesskey=" . sesskey();
10426          foreach ($protocols_available as $protocol => $location) {
10427              $name = get_string('pluginname', 'webservice_'.$protocol);
10428  
10429              $plugin = new stdClass();
10430              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/version.php')) {
10431                  include($CFG->dirroot.'/webservice/'.$protocol.'/version.php');
10432              }
10433              $version = isset($plugin->version) ? $plugin->version : '';
10434  
10435              // hide/show link
10436              if (in_array($protocol, $activeprotocols)) {
10437                  $hideshow = "<a href=\"$url&amp;action=disable&amp;webservice=$protocol\">";
10438                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
10439                  $displayname = "<span>$name</span>";
10440              } else {
10441                  $hideshow = "<a href=\"$url&amp;action=enable&amp;webservice=$protocol\">";
10442                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
10443                  $displayname = "<span class=\"dimmed_text\">$name</span>";
10444              }
10445  
10446              // settings link
10447              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/settings.php')) {
10448                  $settings = "<a href=\"settings.php?section=webservicesetting$protocol\">$strsettings</a>";
10449              } else {
10450                  $settings = '';
10451              }
10452  
10453              // add a row to the table
10454              $table->data[] = array($displayname, $version, $hideshow, $settings);
10455          }
10456          $return .= html_writer::table($table);
10457          $return .= get_string('configwebserviceplugins', 'webservice');
10458          $return .= $OUTPUT->box_end();
10459  
10460          return highlight($query, $return);
10461      }
10462  }
10463  
10464  /**
10465   * Colour picker
10466   *
10467   * @copyright 2010 Sam Hemelryk
10468   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10469   */
10470  class admin_setting_configcolourpicker extends admin_setting {
10471  
10472      /**
10473       * Information for previewing the colour
10474       *
10475       * @var array|null
10476       */
10477      protected $previewconfig = null;
10478  
10479      /**
10480       * Use default when empty.
10481       */
10482      protected $usedefaultwhenempty = true;
10483  
10484      /**
10485       *
10486       * @param string $name
10487       * @param string $visiblename
10488       * @param string $description
10489       * @param string $defaultsetting
10490       * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor');
10491       */
10492      public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig = null,
10493              $usedefaultwhenempty = true) {
10494          $this->previewconfig = $previewconfig;
10495          $this->usedefaultwhenempty = $usedefaultwhenempty;
10496          parent::__construct($name, $visiblename, $description, $defaultsetting);
10497          $this->set_force_ltr(true);
10498      }
10499  
10500      /**
10501       * Return the setting
10502       *
10503       * @return mixed returns config if successful else null
10504       */
10505      public function get_setting() {
10506          return $this->config_read($this->name);
10507      }
10508  
10509      /**
10510       * Saves the setting
10511       *
10512       * @param string $data
10513       * @return bool
10514       */
10515      public function write_setting($data) {
10516          $data = $this->validate($data);
10517          if ($data === false) {
10518              return  get_string('validateerror', 'admin');
10519          }
10520          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
10521      }
10522  
10523      /**
10524       * Validates the colour that was entered by the user
10525       *
10526       * @param string $data
10527       * @return string|false
10528       */
10529      protected function validate($data) {
10530          /**
10531           * List of valid HTML colour names
10532           *
10533           * @var array
10534           */
10535           $colornames = array(
10536              'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure',
10537              'beige', 'bisque', 'black', 'blanchedalmond', 'blue',
10538              'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse',
10539              'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson',
10540              'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray',
10541              'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta',
10542              'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
10543              'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray',
10544              'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
10545              'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick',
10546              'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro',
10547              'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green',
10548              'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo',
10549              'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen',
10550              'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',
10551              'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen',
10552              'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue',
10553              'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow',
10554              'lime', 'limegreen', 'linen', 'magenta', 'maroon',
10555              'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',
10556              'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
10557              'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',
10558              'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',
10559              'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',
10560              'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',
10561              'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',
10562              'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown',
10563              'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',
10564              'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan',
10565              'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white',
10566              'whitesmoke', 'yellow', 'yellowgreen'
10567          );
10568  
10569          if (preg_match('/^#?([[:xdigit:]]{3}){1,2}$/', $data)) {
10570              if (strpos($data, '#')!==0) {
10571                  $data = '#'.$data;
10572              }
10573              return $data;
10574          } else if (in_array(strtolower($data), $colornames)) {
10575              return $data;
10576          } else if (preg_match('/rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)/i', $data)) {
10577              return $data;
10578          } else if (preg_match('/rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)/i', $data)) {
10579              return $data;
10580          } else if (preg_match('/hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)/i', $data)) {
10581              return $data;
10582          } else if (preg_match('/hsla\(\d{0,3}\, ?\d{0,3}%,\d{0,3}%\, ?\d(\.\d)?\)/i', $data)) {
10583              return $data;
10584          } else if (($data == 'transparent') || ($data == 'currentColor') || ($data == 'inherit')) {
10585              return $data;
10586          } else if (empty($data)) {
10587              if ($this->usedefaultwhenempty){
10588                  return $this->defaultsetting;
10589              } else {
10590                  return '';
10591              }
10592          } else {
10593              return false;
10594          }
10595      }
10596  
10597      /**
10598       * Generates the HTML for the setting
10599       *
10600       * @global moodle_page $PAGE
10601       * @global core_renderer $OUTPUT
10602       * @param string $data
10603       * @param string $query
10604       */
10605      public function output_html($data, $query = '') {
10606          global $PAGE, $OUTPUT;
10607  
10608          $icon = new pix_icon('i/loading', get_string('loading', 'admin'), 'moodle', ['class' => 'loadingicon']);
10609          $context = (object) [
10610              'id' => $this->get_id(),
10611              'name' => $this->get_full_name(),
10612              'value' => $data,
10613              'icon' => $icon->export_for_template($OUTPUT),
10614              'haspreviewconfig' => !empty($this->previewconfig),
10615              'forceltr' => $this->get_force_ltr(),
10616              'readonly' => $this->is_readonly(),
10617          ];
10618  
10619          $element = $OUTPUT->render_from_template('core_admin/setting_configcolourpicker', $context);
10620          $PAGE->requires->js_init_call('M.util.init_colour_picker', array($this->get_id(), $this->previewconfig));
10621  
10622          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '',
10623              $this->get_defaultsetting(), $query);
10624      }
10625  
10626  }
10627  
10628  
10629  /**
10630   * Class used for uploading of one file into file storage,
10631   * the file name is stored in config table.
10632   *
10633   * Please note you need to implement your own '_pluginfile' callback function,
10634   * this setting only stores the file, it does not deal with file serving.
10635   *
10636   * @copyright 2013 Petr Skoda {@link http://skodak.org}
10637   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10638   */
10639  class admin_setting_configstoredfile extends admin_setting {
10640      /** @var array file area options - should be one file only */
10641      protected $options;
10642      /** @var string name of the file area */
10643      protected $filearea;
10644      /** @var int intemid */
10645      protected $itemid;
10646      /** @var string used for detection of changes */
10647      protected $oldhashes;
10648  
10649      /**
10650       * Create new stored file setting.
10651       *
10652       * @param string $name low level setting name
10653       * @param string $visiblename human readable setting name
10654       * @param string $description description of setting
10655       * @param mixed $filearea file area for file storage
10656       * @param int $itemid itemid for file storage
10657       * @param array $options file area options
10658       */
10659      public function __construct($name, $visiblename, $description, $filearea, $itemid = 0, array $options = null) {
10660          parent::__construct($name, $visiblename, $description, '');
10661          $this->filearea = $filearea;
10662          $this->itemid   = $itemid;
10663          $this->options  = (array)$options;
10664          $this->customcontrol = true;
10665      }
10666  
10667      /**
10668       * Applies defaults and returns all options.
10669       * @return array
10670       */
10671      protected function get_options() {
10672          global $CFG;
10673  
10674          require_once("$CFG->libdir/filelib.php");
10675          require_once("$CFG->dirroot/repository/lib.php");
10676          $defaults = array(
10677              'mainfile' => '', 'subdirs' => 0, 'maxbytes' => -1, 'maxfiles' => 1,
10678              'accepted_types' => '*', 'return_types' => FILE_INTERNAL, 'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED,
10679              'context' => context_system::instance());
10680          foreach($this->options as $k => $v) {
10681              $defaults[$k] = $v;
10682          }
10683  
10684          return $defaults;
10685      }
10686  
10687      public function get_setting() {
10688          return $this->config_read($this->name);
10689      }
10690  
10691      public function write_setting($data) {
10692          global $USER;
10693  
10694          // Let's not deal with validation here, this is for admins only.
10695          $current = $this->get_setting();
10696          if (empty($data) && $current === null) {
10697              // This will be the case when applying default settings (installation).
10698              return ($this->config_write($this->name, '') ? '' : get_string('errorsetting', 'admin'));
10699          } else if (!is_number($data)) {
10700              // Draft item id is expected here!
10701              return get_string('errorsetting', 'admin');
10702          }
10703  
10704          $options = $this->get_options();
10705          $fs = get_file_storage();
10706          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10707  
10708          $this->oldhashes = null;
10709          if ($current) {
10710              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10711              if ($file = $fs->get_file_by_hash($hash)) {
10712                  $this->oldhashes = $file->get_contenthash().$file->get_pathnamehash();
10713              }
10714              unset($file);
10715          }
10716  
10717          if ($fs->file_exists($options['context']->id, $component, $this->filearea, $this->itemid, '/', '.')) {
10718              // Make sure the settings form was not open for more than 4 days and draft areas deleted in the meantime.
10719              // But we can safely ignore that if the destination area is empty, so that the user is not prompt
10720              // with an error because the draft area does not exist, as he did not use it.
10721              $usercontext = context_user::instance($USER->id);
10722              if (!$fs->file_exists($usercontext->id, 'user', 'draft', $data, '/', '.') && $current !== '') {
10723                  return get_string('errorsetting', 'admin');
10724              }
10725          }
10726  
10727          file_save_draft_area_files($data, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10728          $files = $fs->get_area_files($options['context']->id, $component, $this->filearea, $this->itemid, 'sortorder,filepath,filename', false);
10729  
10730          $filepath = '';
10731          if ($files) {
10732              /** @var stored_file $file */
10733              $file = reset($files);
10734              $filepath = $file->get_filepath().$file->get_filename();
10735          }
10736  
10737          return ($this->config_write($this->name, $filepath) ? '' : get_string('errorsetting', 'admin'));
10738      }
10739  
10740      public function post_write_settings($original) {
10741          $options = $this->get_options();
10742          $fs = get_file_storage();
10743          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10744  
10745          $current = $this->get_setting();
10746          $newhashes = null;
10747          if ($current) {
10748              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10749              if ($file = $fs->get_file_by_hash($hash)) {
10750                  $newhashes = $file->get_contenthash().$file->get_pathnamehash();
10751              }
10752              unset($file);
10753          }
10754  
10755          if ($this->oldhashes === $newhashes) {
10756              $this->oldhashes = null;
10757              return false;
10758          }
10759          $this->oldhashes = null;
10760  
10761          $callbackfunction = $this->updatedcallback;
10762          if (!empty($callbackfunction) and function_exists($callbackfunction)) {
10763              $callbackfunction($this->get_full_name());
10764          }
10765          return true;
10766      }
10767  
10768      public function output_html($data, $query = '') {
10769          global $PAGE, $CFG;
10770  
10771          $options = $this->get_options();
10772          $id = $this->get_id();
10773          $elname = $this->get_full_name();
10774          $draftitemid = file_get_submitted_draft_itemid($elname);
10775          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10776          file_prepare_draft_area($draftitemid, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10777  
10778          // Filemanager form element implementation is far from optimal, we need to rework this if we ever fix it...
10779          require_once("$CFG->dirroot/lib/form/filemanager.php");
10780  
10781          $fmoptions = new stdClass();
10782          $fmoptions->mainfile       = $options['mainfile'];
10783          $fmoptions->maxbytes       = $options['maxbytes'];
10784          $fmoptions->maxfiles       = $options['maxfiles'];
10785          $fmoptions->client_id      = uniqid();
10786          $fmoptions->itemid         = $draftitemid;
10787          $fmoptions->subdirs        = $options['subdirs'];
10788          $fmoptions->target         = $id;
10789          $fmoptions->accepted_types = $options['accepted_types'];
10790          $fmoptions->return_types   = $options['return_types'];
10791          $fmoptions->context        = $options['context'];
10792          $fmoptions->areamaxbytes   = $options['areamaxbytes'];
10793  
10794          $fm = new form_filemanager($fmoptions);
10795          $output = $PAGE->get_renderer('core', 'files');
10796          $html = $output->render($fm);
10797  
10798          $html .= '<input value="'.$draftitemid.'" name="'.$elname.'" type="hidden" />';
10799          $html .= '<input value="" id="'.$id.'" type="hidden" />';
10800  
10801          return format_admin_setting($this, $this->visiblename,
10802              '<div class="form-filemanager" data-fieldtype="filemanager">'.$html.'</div>',
10803              $this->description, true, '', '', $query);
10804      }
10805  }
10806  
10807  
10808  /**
10809   * Administration interface for user specified regular expressions for device detection.
10810   *
10811   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10812   */
10813  class admin_setting_devicedetectregex extends admin_setting {
10814  
10815      /**
10816       * Calls parent::__construct with specific args
10817       *
10818       * @param string $name
10819       * @param string $visiblename
10820       * @param string $description
10821       * @param mixed $defaultsetting
10822       */
10823      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
10824          global $CFG;
10825          parent::__construct($name, $visiblename, $description, $defaultsetting);
10826      }
10827  
10828      /**
10829       * Return the current setting(s)
10830       *
10831       * @return array Current settings array
10832       */
10833      public function get_setting() {
10834          global $CFG;
10835  
10836          $config = $this->config_read($this->name);
10837          if (is_null($config)) {
10838              return null;
10839          }
10840  
10841          return $this->prepare_form_data($config);
10842      }
10843  
10844      /**
10845       * Save selected settings
10846       *
10847       * @param array $data Array of settings to save
10848       * @return bool
10849       */
10850      public function write_setting($data) {
10851          if (empty($data)) {
10852              $data = array();
10853          }
10854  
10855          if ($this->config_write($this->name, $this->process_form_data($data))) {
10856              return ''; // success
10857          } else {
10858              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
10859          }
10860      }
10861  
10862      /**
10863       * Return XHTML field(s) for regexes
10864       *
10865       * @param array $data Array of options to set in HTML
10866       * @return string XHTML string for the fields and wrapping div(s)
10867       */
10868      public function output_html($data, $query='') {
10869          global $OUTPUT;
10870  
10871          $context = (object) [
10872              'expressions' => [],
10873              'name' => $this->get_full_name()
10874          ];
10875  
10876          if (empty($data)) {
10877              $looplimit = 1;
10878          } else {
10879              $looplimit = (count($data)/2)+1;
10880          }
10881  
10882          for ($i=0; $i<$looplimit; $i++) {
10883  
10884              $expressionname = 'expression'.$i;
10885  
10886              if (!empty($data[$expressionname])){
10887                  $expression = $data[$expressionname];
10888              } else {
10889                  $expression = '';
10890              }
10891  
10892              $valuename = 'value'.$i;
10893  
10894              if (!empty($data[$valuename])){
10895                  $value = $data[$valuename];
10896              } else {
10897                  $value= '';
10898              }
10899  
10900              $context->expressions[] = [
10901                  'index' => $i,
10902                  'expression' => $expression,
10903                  'value' => $value
10904              ];
10905          }
10906  
10907          $element = $OUTPUT->render_from_template('core_admin/setting_devicedetectregex', $context);
10908  
10909          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
10910      }
10911  
10912      /**
10913       * Converts the string of regexes
10914       *
10915       * @see self::process_form_data()
10916       * @param $regexes string of regexes
10917       * @return array of form fields and their values
10918       */
10919      protected function prepare_form_data($regexes) {
10920  
10921          $regexes = json_decode($regexes);
10922  
10923          $form = array();
10924  
10925          $i = 0;
10926  
10927          foreach ($regexes as $value => $regex) {
10928              $expressionname  = 'expression'.$i;
10929              $valuename = 'value'.$i;
10930  
10931              $form[$expressionname] = $regex;
10932              $form[$valuename] = $value;
10933              $i++;
10934          }
10935  
10936          return $form;
10937      }
10938  
10939      /**
10940       * Converts the data from admin settings form into a string of regexes
10941       *
10942       * @see self::prepare_form_data()
10943       * @param array $data array of admin form fields and values
10944       * @return false|string of regexes
10945       */
10946      protected function process_form_data(array $form) {
10947  
10948          $count = count($form); // number of form field values
10949  
10950          if ($count % 2) {
10951              // we must get five fields per expression
10952              return false;
10953          }
10954  
10955          $regexes = array();
10956          for ($i = 0; $i < $count / 2; $i++) {
10957              $expressionname  = "expression".$i;
10958              $valuename       = "value".$i;
10959  
10960              $expression = trim($form['expression'.$i]);
10961              $value      = trim($form['value'.$i]);
10962  
10963              if (empty($expression)){
10964                  continue;
10965              }
10966  
10967              $regexes[$value] = $expression;
10968          }
10969  
10970          $regexes = json_encode($regexes);
10971  
10972          return $regexes;
10973      }
10974  
10975  }
10976  
10977  /**
10978   * Multiselect for current modules
10979   *
10980   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10981   */
10982  class admin_setting_configmultiselect_modules extends admin_setting_configmultiselect {
10983      private $excludesystem;
10984  
10985      /**
10986       * Calls parent::__construct - note array $choices is not required
10987       *
10988       * @param string $name setting name
10989       * @param string $visiblename localised setting name
10990       * @param string $description setting description
10991       * @param array $defaultsetting a plain array of default module ids
10992       * @param bool $excludesystem If true, excludes modules with 'system' archetype
10993       */
10994      public function __construct($name, $visiblename, $description, $defaultsetting = array(),
10995              $excludesystem = true) {
10996          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
10997          $this->excludesystem = $excludesystem;
10998      }
10999  
11000      /**
11001       * Loads an array of current module choices
11002       *
11003       * @return bool always return true
11004       */
11005      public function load_choices() {
11006          if (is_array($this->choices)) {
11007              return true;
11008          }
11009          $this->choices = array();
11010  
11011          global $CFG, $DB;
11012          $records = $DB->get_records('modules', array('visible'=>1), 'name');
11013          foreach ($records as $record) {
11014              // Exclude modules if the code doesn't exist
11015              if (file_exists("$CFG->dirroot/mod/$record->name/lib.php")) {
11016                  // Also exclude system modules (if specified)
11017                  if (!($this->excludesystem &&
11018                          plugin_supports('mod', $record->name, FEATURE_MOD_ARCHETYPE) ===
11019                          MOD_ARCHETYPE_SYSTEM)) {
11020                      $this->choices[$record->id] = $record->name;
11021                  }
11022              }
11023          }
11024          return true;
11025      }
11026  }
11027  
11028  /**
11029   * Admin setting to show if a php extension is enabled or not.
11030   *
11031   * @copyright 2013 Damyon Wiese
11032   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11033   */
11034  class admin_setting_php_extension_enabled extends admin_setting {
11035  
11036      /** @var string The name of the extension to check for */
11037      private $extension;
11038  
11039      /**
11040       * Calls parent::__construct with specific arguments
11041       */
11042      public function __construct($name, $visiblename, $description, $extension) {
11043          $this->extension = $extension;
11044          $this->nosave = true;
11045          parent::__construct($name, $visiblename, $description, '');
11046      }
11047  
11048      /**
11049       * Always returns true, does nothing
11050       *
11051       * @return true
11052       */
11053      public function get_setting() {
11054          return true;
11055      }
11056  
11057      /**
11058       * Always returns true, does nothing
11059       *
11060       * @return true
11061       */
11062      public function get_defaultsetting() {
11063          return true;
11064      }
11065  
11066      /**
11067       * Always returns '', does not write anything
11068       *
11069       * @return string Always returns ''
11070       */
11071      public function write_setting($data) {
11072          // Do not write any setting.
11073          return '';
11074      }
11075  
11076      /**
11077       * Outputs the html for this setting.
11078       * @return string Returns an XHTML string
11079       */
11080      public function output_html($data, $query='') {
11081          global $OUTPUT;
11082  
11083          $o = '';
11084          if (!extension_loaded($this->extension)) {
11085              $warning = $OUTPUT->pix_icon('i/warning', '', '', array('role' => 'presentation')) . ' ' . $this->description;
11086  
11087              $o .= format_admin_setting($this, $this->visiblename, $warning);
11088          }
11089          return $o;
11090      }
11091  }
11092  
11093  /**
11094   * Server timezone setting.
11095   *
11096   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11097   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11098   * @author    Petr Skoda <petr.skoda@totaralms.com>
11099   */
11100  class admin_setting_servertimezone extends admin_setting_configselect {
11101      /**
11102       * Constructor.
11103       */
11104      public function __construct() {
11105          $default = core_date::get_default_php_timezone();
11106          if ($default === 'UTC') {
11107              // Nobody really wants UTC, so instead default selection to the country that is confused by the UTC the most.
11108              $default = 'Europe/London';
11109          }
11110  
11111          parent::__construct('timezone',
11112              new lang_string('timezone', 'core_admin'),
11113              new lang_string('configtimezone', 'core_admin'), $default, null);
11114      }
11115  
11116      /**
11117       * Lazy load timezone options.
11118       * @return bool true if loaded, false if error
11119       */
11120      public function load_choices() {
11121          global $CFG;
11122          if (is_array($this->choices)) {
11123              return true;
11124          }
11125  
11126          $current = isset($CFG->timezone) ? $CFG->timezone : null;
11127          $this->choices = core_date::get_list_of_timezones($current, false);
11128          if ($current == 99) {
11129              // Do not show 99 unless it is current value, we want to get rid of it over time.
11130              $this->choices['99'] = new lang_string('timezonephpdefault', 'core_admin',
11131                  core_date::get_default_php_timezone());
11132          }
11133  
11134          return true;
11135      }
11136  }
11137  
11138  /**
11139   * Forced user timezone setting.
11140   *
11141   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11142   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11143   * @author    Petr Skoda <petr.skoda@totaralms.com>
11144   */
11145  class admin_setting_forcetimezone extends admin_setting_configselect {
11146      /**
11147       * Constructor.
11148       */
11149      public function __construct() {
11150          parent::__construct('forcetimezone',
11151              new lang_string('forcetimezone', 'core_admin'),
11152              new lang_string('helpforcetimezone', 'core_admin'), '99', null);
11153      }
11154  
11155      /**
11156       * Lazy load timezone options.
11157       * @return bool true if loaded, false if error
11158       */
11159      public function load_choices() {
11160          global $CFG;
11161          if (is_array($this->choices)) {
11162              return true;
11163          }
11164  
11165          $current = isset($CFG->forcetimezone) ? $CFG->forcetimezone : null;
11166          $this->choices = core_date::get_list_of_timezones($current, true);
11167          $this->choices['99'] = new lang_string('timezonenotforced', 'core_admin');
11168  
11169          return true;
11170      }
11171  }
11172  
11173  
11174  /**
11175   * Search setup steps info.
11176   *
11177   * @package core
11178   * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
11179   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11180   */
11181  class admin_setting_searchsetupinfo extends admin_setting {
11182  
11183      /**
11184       * Calls parent::__construct with specific arguments
11185       */
11186      public function __construct() {
11187          $this->nosave = true;
11188          parent::__construct('searchsetupinfo', '', '', '');
11189      }
11190  
11191      /**
11192       * Always returns true, does nothing
11193       *
11194       * @return true
11195       */
11196      public function get_setting() {
11197          return true;
11198      }
11199  
11200      /**
11201       * Always returns true, does nothing
11202       *
11203       * @return true
11204       */
11205      public function get_defaultsetting() {
11206          return true;
11207      }
11208  
11209      /**
11210       * Always returns '', does not write anything
11211       *
11212       * @param array $data
11213       * @return string Always returns ''
11214       */
11215      public function write_setting($data) {
11216          // Do not write any setting.
11217          return '';
11218      }
11219  
11220      /**
11221       * Builds the HTML to display the control
11222       *
11223       * @param string $data Unused
11224       * @param string $query
11225       * @return string
11226       */
11227      public function output_html($data, $query='') {
11228          global $CFG, $OUTPUT, $ADMIN;
11229  
11230          $return = '';
11231          $brtag = html_writer::empty_tag('br');
11232  
11233          $searchareas = \core_search\manager::get_search_areas_list();
11234          $anyenabled = !empty(\core_search\manager::get_search_areas_list(true));
11235          $anyindexed = false;
11236          foreach ($searchareas as $areaid => $searcharea) {
11237              list($componentname, $varname) = $searcharea->get_config_var_name();
11238              if (get_config($componentname, $varname . '_indexingstart')) {
11239                  $anyindexed = true;
11240                  break;
11241              }
11242          }
11243  
11244          $return .= $OUTPUT->heading(get_string('searchsetupinfo', 'admin'), 3, 'main');
11245  
11246          $table = new html_table();
11247          $table->head = array(get_string('step', 'search'), get_string('status'));
11248          $table->colclasses = array('leftalign step', 'leftalign status');
11249          $table->id = 'searchsetup';
11250          $table->attributes['class'] = 'admintable generaltable';
11251          $table->data = array();
11252  
11253          $return .= $brtag . get_string('searchsetupdescription', 'search') . $brtag . $brtag;
11254  
11255          // Select a search engine.
11256          $row = array();
11257          $url = new moodle_url('/admin/settings.php?section=manageglobalsearch#admin-searchengine');
11258          $row[0] = '1. ' . html_writer::tag('a', get_string('selectsearchengine', 'admin'),
11259                          array('href' => $url));
11260  
11261          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11262          if (!empty($CFG->searchengine)) {
11263              $status = html_writer::tag('span', get_string('pluginname', 'search_' . $CFG->searchengine),
11264                  array('class' => 'badge badge-success'));
11265  
11266          }
11267          $row[1] = $status;
11268          $table->data[] = $row;
11269  
11270          // Available areas.
11271          $row = array();
11272          $url = new moodle_url('/admin/searchareas.php');
11273          $row[0] = '2. ' . html_writer::tag('a', get_string('enablesearchareas', 'admin'),
11274                          array('href' => $url));
11275  
11276          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11277          if ($anyenabled) {
11278              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11279  
11280          }
11281          $row[1] = $status;
11282          $table->data[] = $row;
11283  
11284          // Setup search engine.
11285          $row = array();
11286          if (empty($CFG->searchengine)) {
11287              $row[0] = '3. ' . get_string('setupsearchengine', 'admin');
11288              $row[1] = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11289          } else {
11290              if ($ADMIN->locate('search' . $CFG->searchengine)) {
11291                  $url = new moodle_url('/admin/settings.php?section=search' . $CFG->searchengine);
11292                  $row[0] = '3. ' . html_writer::link($url, get_string('setupsearchengine', 'core_admin'));
11293              } else {
11294                  $row[0] = '3. ' . get_string('setupsearchengine', 'core_admin');
11295              }
11296  
11297              // Check the engine status.
11298              $searchengine = \core_search\manager::search_engine_instance();
11299              try {
11300                  $serverstatus = $searchengine->is_server_ready();
11301              } catch (\moodle_exception $e) {
11302                  $serverstatus = $e->getMessage();
11303              }
11304              if ($serverstatus === true) {
11305                  $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11306              } else {
11307                  $status = html_writer::tag('span', $serverstatus, array('class' => 'badge badge-danger'));
11308              }
11309              $row[1] = $status;
11310          }
11311          $table->data[] = $row;
11312  
11313          // Indexed data.
11314          $row = array();
11315          $url = new moodle_url('/admin/searchareas.php');
11316          $row[0] = '4. ' . html_writer::tag('a', get_string('indexdata', 'admin'), array('href' => $url));
11317          if ($anyindexed) {
11318              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11319          } else {
11320              $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11321          }
11322          $row[1] = $status;
11323          $table->data[] = $row;
11324  
11325          // Enable global search.
11326          $row = array();
11327          $url = new moodle_url("/admin/search.php?query=enableglobalsearch");
11328          $row[0] = '5. ' . html_writer::tag('a', get_string('enableglobalsearch', 'admin'),
11329                          array('href' => $url));
11330          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11331          if (\core_search\manager::is_global_search_enabled()) {
11332              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11333          }
11334          $row[1] = $status;
11335          $table->data[] = $row;
11336  
11337          // Replace front page search.
11338          $row = array();
11339          $url = new moodle_url("/admin/search.php?query=searchincludeallcourses");
11340          $row[0] = '6. ' . html_writer::tag('a', get_string('replacefrontsearch', 'admin'),
11341                                             array('href' => $url));
11342          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11343          if (\core_search\manager::can_replace_course_search()) {
11344              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11345          }
11346          $row[1] = $status;
11347          $table->data[] = $row;
11348  
11349          $return .= html_writer::table($table);
11350  
11351          return highlight($query, $return);
11352      }
11353  
11354  }
11355  
11356  /**
11357   * Used to validate the contents of SCSS code and ensuring they are parsable.
11358   *
11359   * It does not attempt to detect undefined SCSS variables because it is designed
11360   * to be used without knowledge of other config/scss included.
11361   *
11362   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11363   * @copyright 2016 Dan Poltawski <dan@moodle.com>
11364   */
11365  class admin_setting_scsscode extends admin_setting_configtextarea {
11366  
11367      /**
11368       * Validate the contents of the SCSS to ensure its parsable. Does not
11369       * attempt to detect undefined scss variables.
11370       *
11371       * @param string $data The scss code from text field.
11372       * @return mixed bool true for success or string:error on failure.
11373       */
11374      public function validate($data) {
11375          if (empty($data)) {
11376              return true;
11377          }
11378  
11379          $scss = new core_scss();
11380          try {
11381              $scss->compile($data);
11382          } catch (ScssPhp\ScssPhp\Exception\ParserException $e) {
11383              return get_string('scssinvalid', 'admin', $e->getMessage());
11384          } catch (ScssPhp\ScssPhp\Exception\CompilerException $e) {
11385              // Silently ignore this - it could be a scss variable defined from somewhere
11386              // else which we are not examining here.
11387              return true;
11388          }
11389  
11390          return true;
11391      }
11392  }
11393  
11394  
11395  /**
11396   * Administration setting to define a list of file types.
11397   *
11398   * @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
11399   * @copyright 2017 David Mudrák <david@moodle.com>
11400   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11401   */
11402  class admin_setting_filetypes extends admin_setting_configtext {
11403  
11404      /** @var array Allow selection from these file types only. */
11405      protected $onlytypes = [];
11406  
11407      /** @var bool Allow selection of 'All file types' (will be stored as '*'). */
11408      protected $allowall = true;
11409  
11410      /** @var core_form\filetypes_util instance to use as a helper. */
11411      protected $util = null;
11412  
11413      /**
11414       * Constructor.
11415       *
11416       * @param string $name Unique ascii name like 'mycoresetting' or 'myplugin/mysetting'
11417       * @param string $visiblename Localised label of the setting
11418       * @param string $description Localised description of the setting
11419       * @param string $defaultsetting Default setting value.
11420       * @param array $options Setting widget options, an array with optional keys:
11421       *   'onlytypes' => array Allow selection from these file types only; for example ['onlytypes' => ['web_image']].
11422       *   'allowall' => bool Allow to select 'All file types', defaults to true. Does not apply if onlytypes are set.
11423       */
11424      public function __construct($name, $visiblename, $description, $defaultsetting = '', array $options = []) {
11425  
11426          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW);
11427  
11428          if (array_key_exists('onlytypes', $options) && is_array($options['onlytypes'])) {
11429              $this->onlytypes = $options['onlytypes'];
11430          }
11431  
11432          if (!$this->onlytypes && array_key_exists('allowall', $options)) {
11433              $this->allowall = (bool)$options['allowall'];
11434          }
11435  
11436          $this->util = new \core_form\filetypes_util();
11437      }
11438  
11439      /**
11440       * Normalize the user's input and write it to the database as comma separated list.
11441       *
11442       * Comma separated list as a text representation of the array was chosen to
11443       * make this compatible with how the $CFG->courseoverviewfilesext values are stored.
11444       *
11445       * @param string $data Value submitted by the admin.
11446       * @return string Epty string if all good, error message otherwise.
11447       */
11448      public function write_setting($data) {
11449          return parent::write_setting(implode(',', $this->util->normalize_file_types($data)));
11450      }
11451  
11452      /**
11453       * Validate data before storage
11454       *
11455       * @param string $data The setting values provided by the admin
11456       * @return bool|string True if ok, the string if error found
11457       */
11458      public function validate($data) {
11459          $parentcheck = parent::validate($data);
11460          if ($parentcheck !== true) {
11461              return $parentcheck;
11462          }
11463  
11464          // Check for unknown file types.
11465          if ($unknown = $this->util->get_unknown_file_types($data)) {
11466              return get_string('filetypesunknown', 'core_form', implode(', ', $unknown));
11467          }
11468  
11469          // Check for disallowed file types.
11470          if ($notlisted = $this->util->get_not_listed($data, $this->onlytypes)) {
11471              return get_string('filetypesnotallowed', 'core_form', implode(', ', $notlisted));
11472          }
11473  
11474          return true;
11475      }
11476  
11477      /**
11478       * Return an HTML string for the setting element.
11479       *
11480       * @param string $data The current setting value
11481       * @param string $query Admin search query to be highlighted
11482       * @return string HTML to be displayed
11483       */
11484      public function output_html($data, $query='') {
11485          global $OUTPUT, $PAGE;
11486  
11487          $default = $this->get_defaultsetting();
11488          $context = (object) [
11489              'id' => $this->get_id(),
11490              'name' => $this->get_full_name(),
11491              'value' => $data,
11492              'descriptions' => $this->util->describe_file_types($data),
11493          ];
11494          $element = $OUTPUT->render_from_template('core_admin/setting_filetypes', $context);
11495  
11496          $PAGE->requires->js_call_amd('core_form/filetypes', 'init', [
11497              $this->get_id(),
11498              $this->visiblename->out(),
11499              $this->onlytypes,
11500              $this->allowall,
11501          ]);
11502  
11503          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
11504      }
11505  
11506      /**
11507       * Should the values be always displayed in LTR mode?
11508       *
11509       * We always return true here because these values are not RTL compatible.
11510       *
11511       * @return bool True because these values are not RTL compatible.
11512       */
11513      public function get_force_ltr() {
11514          return true;
11515      }
11516  }
11517  
11518  /**
11519   * Used to validate the content and format of the age of digital consent map and ensuring it is parsable.
11520   *
11521   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11522   * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
11523   */
11524  class admin_setting_agedigitalconsentmap extends admin_setting_configtextarea {
11525  
11526      /**
11527       * Constructor.
11528       *
11529       * @param string $name
11530       * @param string $visiblename
11531       * @param string $description
11532       * @param mixed $defaultsetting string or array
11533       * @param mixed $paramtype
11534       * @param string $cols
11535       * @param string $rows
11536       */
11537      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype = PARAM_RAW,
11538                                  $cols = '60', $rows = '8') {
11539          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
11540          // Pre-set force LTR to false.
11541          $this->set_force_ltr(false);
11542      }
11543  
11544      /**
11545       * Validate the content and format of the age of digital consent map to ensure it is parsable.
11546       *
11547       * @param string $data The age of digital consent map from text field.
11548       * @return mixed bool true for success or string:error on failure.
11549       */
11550      public function validate($data) {
11551          if (empty($data)) {
11552              return true;
11553          }
11554  
11555          try {
11556              \core_auth\digital_consent::parse_age_digital_consent_map($data);
11557          } catch (\moodle_exception $e) {
11558              return get_string('invalidagedigitalconsent', 'admin', $e->getMessage());
11559          }
11560  
11561          return true;
11562      }
11563  }
11564  
11565  /**
11566   * Selection of plugins that can work as site policy handlers
11567   *
11568   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11569   * @copyright 2018 Marina Glancy
11570   */
11571  class admin_settings_sitepolicy_handler_select extends admin_setting_configselect {
11572  
11573      /**
11574       * Constructor
11575       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11576       *        for ones in config_plugins.
11577       * @param string $visiblename localised
11578       * @param string $description long localised info
11579       * @param string $defaultsetting
11580       */
11581      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11582          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11583      }
11584  
11585      /**
11586       * Lazy-load the available choices for the select box
11587       */
11588      public function load_choices() {
11589          if (during_initial_install()) {
11590              return false;
11591          }
11592          if (is_array($this->choices)) {
11593              return true;
11594          }
11595  
11596          $this->choices = ['' => new lang_string('sitepolicyhandlercore', 'core_admin')];
11597          $manager = new \core_privacy\local\sitepolicy\manager();
11598          $plugins = $manager->get_all_handlers();
11599          foreach ($plugins as $pname => $unused) {
11600              $this->choices[$pname] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11601                  ['name' => new lang_string('pluginname', $pname), 'component' => $pname]);
11602          }
11603  
11604          return true;
11605      }
11606  }
11607  
11608  /**
11609   * Used to validate theme presets code and ensuring they compile well.
11610   *
11611   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11612   * @copyright 2019 Bas Brands <bas@moodle.com>
11613   */
11614  class admin_setting_configthemepreset extends admin_setting_configselect {
11615  
11616      /** @var string The name of the theme to check for */
11617      private $themename;
11618  
11619      /**
11620       * Constructor
11621       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
11622       * or 'myplugin/mysetting' for ones in config_plugins.
11623       * @param string $visiblename localised
11624       * @param string $description long localised info
11625       * @param string|int $defaultsetting
11626       * @param array $choices array of $value=>$label for each selection
11627       * @param string $themename name of theme to check presets for.
11628       */
11629      public function __construct($name, $visiblename, $description, $defaultsetting, $choices, $themename) {
11630          $this->themename = $themename;
11631          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
11632      }
11633  
11634      /**
11635       * Write settings if validated
11636       *
11637       * @param string $data
11638       * @return string
11639       */
11640      public function write_setting($data) {
11641          $validated = $this->validate($data);
11642          if ($validated !== true) {
11643              return $validated;
11644          }
11645          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
11646      }
11647  
11648      /**
11649       * Validate the preset file to ensure its parsable.
11650       *
11651       * @param string $data The preset file chosen.
11652       * @return mixed bool true for success or string:error on failure.
11653       */
11654      public function validate($data) {
11655  
11656          if (in_array($data, ['default.scss', 'plain.scss'])) {
11657              return true;
11658          }
11659  
11660          $fs = get_file_storage();
11661          $theme = theme_config::load($this->themename);
11662          $context = context_system::instance();
11663  
11664          // If the preset has not changed there is no need to validate it.
11665          if ($theme->settings->preset == $data) {
11666              return true;
11667          }
11668  
11669          if ($presetfile = $fs->get_file($context->id, 'theme_' . $this->themename, 'preset', 0, '/', $data)) {
11670              // This operation uses a lot of resources.
11671              raise_memory_limit(MEMORY_EXTRA);
11672              core_php_time_limit::raise(300);
11673  
11674              // TODO: MDL-62757 When changing anything in this method please do not forget to check
11675              // if the get_css_content_from_scss() method in class theme_config needs updating too.
11676  
11677              $compiler = new core_scss();
11678              $compiler->prepend_raw_scss($theme->get_pre_scss_code());
11679              $compiler->append_raw_scss($presetfile->get_content());
11680              if ($scssproperties = $theme->get_scss_property()) {
11681                  $compiler->setImportPaths($scssproperties[0]);
11682              }
11683              $compiler->append_raw_scss($theme->get_extra_scss_code());
11684  
11685              try {
11686                  $compiler->to_css();
11687              } catch (Exception $e) {
11688                  return get_string('invalidthemepreset', 'admin', $e->getMessage());
11689              }
11690  
11691              // Try to save memory.
11692              $compiler = null;
11693              unset($compiler);
11694          }
11695  
11696          return true;
11697      }
11698  }
11699  
11700  /**
11701   * Selection of plugins that can work as H5P libraries handlers
11702   *
11703   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11704   * @copyright 2020 Sara Arjona <sara@moodle.com>
11705   */
11706  class admin_settings_h5plib_handler_select extends admin_setting_configselect {
11707  
11708      /**
11709       * Constructor
11710       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11711       *        for ones in config_plugins.
11712       * @param string $visiblename localised
11713       * @param string $description long localised info
11714       * @param string $defaultsetting
11715       */
11716      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11717          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11718      }
11719  
11720      /**
11721       * Lazy-load the available choices for the select box
11722       */
11723      public function load_choices() {
11724          if (during_initial_install()) {
11725              return false;
11726          }
11727          if (is_array($this->choices)) {
11728              return true;
11729          }
11730  
11731          $this->choices = \core_h5p\local\library\autoloader::get_all_handlers();
11732          foreach ($this->choices as $name => $class) {
11733              $this->choices[$name] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11734                  ['name' => new lang_string('pluginname', $name), 'component' => $name]);
11735          }
11736  
11737          return true;
11738      }
11739  }