Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
/lib/ -> adminlib.php (source)

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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)) {
 370          $DB->get_manager()->delete_tables_from_xmldb_file($file);
 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 ($this->is_forceable() && 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       * Whether the setting can be overridden in config.php.
2166       *
2167       * Returning true will allow the setting to be defined and overridden in config.php.
2168       * Returning false will prevent the config setting from being overridden even when it gets defined in config.php.
2169       *
2170       * @return bool
2171       */
2172      public function is_forceable(): bool {
2173          return true;
2174      }
2175  }
2176  
2177  /**
2178   * An additional option that can be applied to an admin setting.
2179   * The currently supported options are 'ADVANCED', 'LOCKED' and 'REQUIRED'.
2180   *
2181   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2182   */
2183  class admin_setting_flag {
2184      /** @var bool Flag to indicate if this option can be toggled for this setting */
2185      private $enabled = false;
2186      /** @var bool Flag to indicate if this option defaults to true or false */
2187      private $default = false;
2188      /** @var string Short string used to create setting name - e.g. 'adv' */
2189      private $shortname = '';
2190      /** @var string String used as the label for this flag */
2191      private $displayname = '';
2192      /** @const Checkbox for this flag is displayed in admin page */
2193      const ENABLED = true;
2194      /** @const Checkbox for this flag is not displayed in admin page */
2195      const DISABLED = false;
2196  
2197      /**
2198       * Constructor
2199       *
2200       * @param bool $enabled Can this option can be toggled.
2201       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2202       * @param bool $default The default checked state for this setting option.
2203       * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
2204       * @param string $displayname The displayname of this flag. Used as a label for the flag.
2205       */
2206      public function __construct($enabled, $default, $shortname, $displayname) {
2207          $this->shortname = $shortname;
2208          $this->displayname = $displayname;
2209          $this->set_options($enabled, $default);
2210      }
2211  
2212      /**
2213       * Update the values of this setting options class
2214       *
2215       * @param bool $enabled Can this option can be toggled.
2216       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2217       * @param bool $default The default checked state for this setting option.
2218       */
2219      public function set_options($enabled, $default) {
2220          $this->enabled = $enabled;
2221          $this->default = $default;
2222      }
2223  
2224      /**
2225       * Should this option appear in the interface and be toggleable?
2226       *
2227       * @return bool Is it enabled?
2228       */
2229      public function is_enabled() {
2230          return $this->enabled;
2231      }
2232  
2233      /**
2234       * Should this option be checked by default?
2235       *
2236       * @return bool Is it on by default?
2237       */
2238      public function get_default() {
2239          return $this->default;
2240      }
2241  
2242      /**
2243       * Return the short name for this flag. e.g. 'adv' or 'locked'
2244       *
2245       * @return string
2246       */
2247      public function get_shortname() {
2248          return $this->shortname;
2249      }
2250  
2251      /**
2252       * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
2253       *
2254       * @return string
2255       */
2256      public function get_displayname() {
2257          return $this->displayname;
2258      }
2259  
2260      /**
2261       * Save the submitted data for this flag - or set it to the default if $data is null.
2262       *
2263       * @param admin_setting $setting - The admin setting for this flag
2264       * @param array $data - The data submitted from the form or null to set the default value for new installs.
2265       * @return bool
2266       */
2267      public function write_setting_flag(admin_setting $setting, $data) {
2268          $result = true;
2269          if ($this->is_enabled()) {
2270              if (!isset($data)) {
2271                  $value = $this->get_default();
2272              } else {
2273                  $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
2274              }
2275              $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
2276          }
2277  
2278          return $result;
2279  
2280      }
2281  
2282      /**
2283       * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
2284       *
2285       * @param admin_setting $setting - The admin setting for this flag
2286       * @return string - The html for the checkbox.
2287       */
2288      public function output_setting_flag(admin_setting $setting) {
2289          global $OUTPUT;
2290  
2291          $value = $setting->get_setting_flag_value($this);
2292  
2293          $context = new stdClass();
2294          $context->id = $setting->get_id() . '_' . $this->get_shortname();
2295          $context->name = $setting->get_full_name() .  '_' . $this->get_shortname();
2296          $context->value = 1;
2297          $context->checked = $value ? true : false;
2298          $context->label = $this->get_displayname();
2299  
2300          return $OUTPUT->render_from_template('core_admin/setting_flag', $context);
2301      }
2302  }
2303  
2304  
2305  /**
2306   * No setting - just heading and text.
2307   *
2308   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2309   */
2310  class admin_setting_heading extends admin_setting {
2311  
2312      /**
2313       * not a setting, just text
2314       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2315       * @param string $heading heading
2316       * @param string $information text in box
2317       */
2318      public function __construct($name, $heading, $information) {
2319          $this->nosave = true;
2320          parent::__construct($name, $heading, $information, '');
2321      }
2322  
2323      /**
2324       * Always returns true
2325       * @return bool Always returns true
2326       */
2327      public function get_setting() {
2328          return true;
2329      }
2330  
2331      /**
2332       * Always returns true
2333       * @return bool Always returns true
2334       */
2335      public function get_defaultsetting() {
2336          return true;
2337      }
2338  
2339      /**
2340       * Never write settings
2341       * @return string Always returns an empty string
2342       */
2343      public function write_setting($data) {
2344      // do not write any setting
2345          return '';
2346      }
2347  
2348      /**
2349       * Returns an HTML string
2350       * @return string Returns an HTML string
2351       */
2352      public function output_html($data, $query='') {
2353          global $OUTPUT;
2354          $context = new stdClass();
2355          $context->title = $this->visiblename;
2356          $context->description = $this->description;
2357          $context->descriptionformatted = highlight($query, markdown_to_html($this->description));
2358          return $OUTPUT->render_from_template('core_admin/setting_heading', $context);
2359      }
2360  }
2361  
2362  /**
2363   * No setting - just name and description in same row.
2364   *
2365   * @copyright 2018 onwards Amaia Anabitarte
2366   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2367   */
2368  class admin_setting_description extends admin_setting {
2369  
2370      /**
2371       * Not a setting, just text
2372       *
2373       * @param string $name
2374       * @param string $visiblename
2375       * @param string $description
2376       */
2377      public function __construct($name, $visiblename, $description) {
2378          $this->nosave = true;
2379          parent::__construct($name, $visiblename, $description, '');
2380      }
2381  
2382      /**
2383       * Always returns true
2384       *
2385       * @return bool Always returns true
2386       */
2387      public function get_setting() {
2388          return true;
2389      }
2390  
2391      /**
2392       * Always returns true
2393       *
2394       * @return bool Always returns true
2395       */
2396      public function get_defaultsetting() {
2397          return true;
2398      }
2399  
2400      /**
2401       * Never write settings
2402       *
2403       * @param mixed $data Gets converted to str for comparison against yes value
2404       * @return string Always returns an empty string
2405       */
2406      public function write_setting($data) {
2407          // Do not write any setting.
2408          return '';
2409      }
2410  
2411      /**
2412       * Returns an HTML string
2413       *
2414       * @param string $data
2415       * @param string $query
2416       * @return string Returns an HTML string
2417       */
2418      public function output_html($data, $query='') {
2419          global $OUTPUT;
2420  
2421          $context = new stdClass();
2422          $context->title = $this->visiblename;
2423          $context->description = $this->description;
2424  
2425          return $OUTPUT->render_from_template('core_admin/setting_description', $context);
2426      }
2427  }
2428  
2429  
2430  
2431  /**
2432   * The most flexible setting, the user enters text.
2433   *
2434   * This type of field should be used for config settings which are using
2435   * English words and are not localised (passwords, database name, list of values, ...).
2436   *
2437   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2438   */
2439  class admin_setting_configtext extends admin_setting {
2440  
2441      /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
2442      public $paramtype;
2443      /** @var int default field size */
2444      public $size;
2445  
2446      /**
2447       * Config text constructor
2448       *
2449       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2450       * @param string $visiblename localised
2451       * @param string $description long localised info
2452       * @param string $defaultsetting
2453       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2454       * @param int $size default field size
2455       */
2456      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2457          $this->paramtype = $paramtype;
2458          if (!is_null($size)) {
2459              $this->size  = $size;
2460          } else {
2461              $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
2462          }
2463          parent::__construct($name, $visiblename, $description, $defaultsetting);
2464      }
2465  
2466      /**
2467       * Get whether this should be displayed in LTR mode.
2468       *
2469       * Try to guess from the PARAM type unless specifically set.
2470       */
2471      public function get_force_ltr() {
2472          $forceltr = parent::get_force_ltr();
2473          if ($forceltr === null) {
2474              return !is_rtl_compatible($this->paramtype);
2475          }
2476          return $forceltr;
2477      }
2478  
2479      /**
2480       * Return the setting
2481       *
2482       * @return mixed returns config if successful else null
2483       */
2484      public function get_setting() {
2485          return $this->config_read($this->name);
2486      }
2487  
2488      public function write_setting($data) {
2489          if ($this->paramtype === PARAM_INT and $data === '') {
2490          // do not complain if '' used instead of 0
2491              $data = 0;
2492          }
2493          // $data is a string
2494          $validated = $this->validate($data);
2495          if ($validated !== true) {
2496              return $validated;
2497          }
2498          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2499      }
2500  
2501      /**
2502       * Validate data before storage
2503       * @param string data
2504       * @return mixed true if ok string if error found
2505       */
2506      public function validate($data) {
2507          // allow paramtype to be a custom regex if it is the form of /pattern/
2508          if (preg_match('#^/.*/$#', $this->paramtype)) {
2509              if (preg_match($this->paramtype, $data)) {
2510                  return true;
2511              } else {
2512                  return get_string('validateerror', 'admin');
2513              }
2514  
2515          } else if ($this->paramtype === PARAM_RAW) {
2516              return true;
2517  
2518          } else {
2519              $cleaned = clean_param($data, $this->paramtype);
2520              if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
2521                  return true;
2522              } else {
2523                  return get_string('validateerror', 'admin');
2524              }
2525          }
2526      }
2527  
2528      /**
2529       * Return an XHTML string for the setting
2530       * @return string Returns an XHTML string
2531       */
2532      public function output_html($data, $query='') {
2533          global $OUTPUT;
2534  
2535          $default = $this->get_defaultsetting();
2536          $context = (object) [
2537              'size' => $this->size,
2538              'id' => $this->get_id(),
2539              'name' => $this->get_full_name(),
2540              'value' => $data,
2541              'forceltr' => $this->get_force_ltr(),
2542              'readonly' => $this->is_readonly(),
2543          ];
2544          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
2545  
2546          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2547      }
2548  }
2549  
2550  /**
2551   * Text input with a maximum length constraint.
2552   *
2553   * @copyright 2015 onwards Ankit Agarwal
2554   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2555   */
2556  class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
2557  
2558      /** @var int maximum number of chars allowed. */
2559      protected $maxlength;
2560  
2561      /**
2562       * Config text constructor
2563       *
2564       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2565       *                     or 'myplugin/mysetting' for ones in config_plugins.
2566       * @param string $visiblename localised
2567       * @param string $description long localised info
2568       * @param string $defaultsetting
2569       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2570       * @param int $size default field size
2571       * @param mixed $maxlength int maxlength allowed, 0 for infinite.
2572       */
2573      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW,
2574                                  $size=null, $maxlength = 0) {
2575          $this->maxlength = $maxlength;
2576          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
2577      }
2578  
2579      /**
2580       * Validate data before storage
2581       *
2582       * @param string $data data
2583       * @return mixed true if ok string if error found
2584       */
2585      public function validate($data) {
2586          $parentvalidation = parent::validate($data);
2587          if ($parentvalidation === true) {
2588              if ($this->maxlength > 0) {
2589                  // Max length check.
2590                  $length = core_text::strlen($data);
2591                  if ($length > $this->maxlength) {
2592                      return get_string('maximumchars', 'moodle',  $this->maxlength);
2593                  }
2594                  return true;
2595              } else {
2596                  return true; // No max length check needed.
2597              }
2598          } else {
2599              return $parentvalidation;
2600          }
2601      }
2602  }
2603  
2604  /**
2605   * General text area without html editor.
2606   *
2607   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2608   */
2609  class admin_setting_configtextarea extends admin_setting_configtext {
2610      private $rows;
2611      private $cols;
2612  
2613      /**
2614       * @param string $name
2615       * @param string $visiblename
2616       * @param string $description
2617       * @param mixed $defaultsetting string or array
2618       * @param mixed $paramtype
2619       * @param string $cols The number of columns to make the editor
2620       * @param string $rows The number of rows to make the editor
2621       */
2622      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2623          $this->rows = $rows;
2624          $this->cols = $cols;
2625          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
2626      }
2627  
2628      /**
2629       * Returns an XHTML string for the editor
2630       *
2631       * @param string $data
2632       * @param string $query
2633       * @return string XHTML string for the editor
2634       */
2635      public function output_html($data, $query='') {
2636          global $OUTPUT;
2637  
2638          $default = $this->get_defaultsetting();
2639          $defaultinfo = $default;
2640          if (!is_null($default) and $default !== '') {
2641              $defaultinfo = "\n".$default;
2642          }
2643  
2644          $context = (object) [
2645              'cols' => $this->cols,
2646              'rows' => $this->rows,
2647              'id' => $this->get_id(),
2648              'name' => $this->get_full_name(),
2649              'value' => $data,
2650              'forceltr' => $this->get_force_ltr(),
2651              'readonly' => $this->is_readonly(),
2652          ];
2653          $element = $OUTPUT->render_from_template('core_admin/setting_configtextarea', $context);
2654  
2655          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
2656      }
2657  }
2658  
2659  /**
2660   * General text area with html editor.
2661   */
2662  class admin_setting_confightmleditor extends admin_setting_configtextarea {
2663  
2664      /**
2665       * @param string $name
2666       * @param string $visiblename
2667       * @param string $description
2668       * @param mixed $defaultsetting string or array
2669       * @param mixed $paramtype
2670       */
2671      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2672          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
2673          $this->set_force_ltr(false);
2674          editors_head_setup();
2675      }
2676  
2677      /**
2678       * Returns an XHTML string for the editor
2679       *
2680       * @param string $data
2681       * @param string $query
2682       * @return string XHTML string for the editor
2683       */
2684      public function output_html($data, $query='') {
2685          $editor = editors_get_preferred_editor(FORMAT_HTML);
2686          $editor->set_text($data);
2687          $editor->use_editor($this->get_id(), array('noclean'=>true));
2688          return parent::output_html($data, $query);
2689      }
2690  
2691      /**
2692       * Checks if data has empty html.
2693       *
2694       * @param string $data
2695       * @return string Empty when no errors.
2696       */
2697      public function write_setting($data) {
2698          if (trim(html_to_text($data)) === '') {
2699              $data = '';
2700          }
2701          return parent::write_setting($data);
2702      }
2703  }
2704  
2705  
2706  /**
2707   * Password field, allows unmasking of password
2708   *
2709   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2710   */
2711  class admin_setting_configpasswordunmask extends admin_setting_configtext {
2712  
2713      /**
2714       * Constructor
2715       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2716       * @param string $visiblename localised
2717       * @param string $description long localised info
2718       * @param string $defaultsetting default password
2719       */
2720      public function __construct($name, $visiblename, $description, $defaultsetting) {
2721          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2722      }
2723  
2724      /**
2725       * Log config changes if necessary.
2726       * @param string $name
2727       * @param string $oldvalue
2728       * @param string $value
2729       */
2730      protected function add_to_config_log($name, $oldvalue, $value) {
2731          if ($value !== '') {
2732              $value = '********';
2733          }
2734          if ($oldvalue !== '' and $oldvalue !== null) {
2735              $oldvalue = '********';
2736          }
2737          parent::add_to_config_log($name, $oldvalue, $value);
2738      }
2739  
2740      /**
2741       * Returns HTML for the field.
2742       *
2743       * @param   string  $data       Value for the field
2744       * @param   string  $query      Passed as final argument for format_admin_setting
2745       * @return  string              Rendered HTML
2746       */
2747      public function output_html($data, $query='') {
2748          global $OUTPUT;
2749  
2750          $context = (object) [
2751              'id' => $this->get_id(),
2752              'name' => $this->get_full_name(),
2753              'size' => $this->size,
2754              'value' => $this->is_readonly() ? null : $data,
2755              'forceltr' => $this->get_force_ltr(),
2756              'readonly' => $this->is_readonly(),
2757          ];
2758          $element = $OUTPUT->render_from_template('core_admin/setting_configpasswordunmask', $context);
2759          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', null, $query);
2760      }
2761  }
2762  
2763  /**
2764   * Password field, allows unmasking of password, with an advanced checkbox that controls an additional $name.'_adv' setting.
2765   *
2766   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2767   * @copyright 2018 Paul Holden (pholden@greenhead.ac.uk)
2768   */
2769  class admin_setting_configpasswordunmask_with_advanced extends admin_setting_configpasswordunmask {
2770  
2771      /**
2772       * Constructor
2773       *
2774       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2775       * @param string $visiblename localised
2776       * @param string $description long localised info
2777       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
2778       */
2779      public function __construct($name, $visiblename, $description, $defaultsetting) {
2780          parent::__construct($name, $visiblename, $description, $defaultsetting['value']);
2781          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
2782      }
2783  }
2784  
2785  /**
2786   * Admin setting class for encrypted values using secure encryption.
2787   *
2788   * @copyright 2019 The Open University
2789   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2790   */
2791  class admin_setting_encryptedpassword extends admin_setting {
2792  
2793      /**
2794       * Constructor. Same as parent except that the default value is always an empty string.
2795       *
2796       * @param string $name Internal name used in config table
2797       * @param string $visiblename Name shown on form
2798       * @param string $description Description that appears below field
2799       */
2800      public function __construct(string $name, string $visiblename, string $description) {
2801          parent::__construct($name, $visiblename, $description, '');
2802      }
2803  
2804      public function get_setting() {
2805          return $this->config_read($this->name);
2806      }
2807  
2808      public function write_setting($data) {
2809          $data = trim($data);
2810          if ($data === '') {
2811              // Value can really be set to nothing.
2812              $savedata = '';
2813          } else {
2814              // Encrypt value before saving it.
2815              $savedata = \core\encryption::encrypt($data);
2816          }
2817          return ($this->config_write($this->name, $savedata) ? '' : get_string('errorsetting', 'admin'));
2818      }
2819  
2820      public function output_html($data, $query='') {
2821          global $OUTPUT;
2822  
2823          $default = $this->get_defaultsetting();
2824          $context = (object) [
2825              'id' => $this->get_id(),
2826              'name' => $this->get_full_name(),
2827              'set' => $data !== '',
2828              'novalue' => $this->get_setting() === null
2829          ];
2830          $element = $OUTPUT->render_from_template('core_admin/setting_encryptedpassword', $context);
2831  
2832          return format_admin_setting($this, $this->visiblename, $element, $this->description,
2833                  true, '', $default, $query);
2834      }
2835  }
2836  
2837  /**
2838   * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
2839   * Note: Only advanced makes sense right now - locked does not.
2840   *
2841   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2842   */
2843  class admin_setting_configempty extends admin_setting_configtext {
2844  
2845      /**
2846       * @param string $name
2847       * @param string $visiblename
2848       * @param string $description
2849       */
2850      public function __construct($name, $visiblename, $description) {
2851          parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
2852      }
2853  
2854      /**
2855       * Returns an XHTML string for the hidden field
2856       *
2857       * @param string $data
2858       * @param string $query
2859       * @return string XHTML string for the editor
2860       */
2861      public function output_html($data, $query='') {
2862          global $OUTPUT;
2863  
2864          $context = (object) [
2865              'id' => $this->get_id(),
2866              'name' => $this->get_full_name()
2867          ];
2868          $element = $OUTPUT->render_from_template('core_admin/setting_configempty', $context);
2869  
2870          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', get_string('none'), $query);
2871      }
2872  }
2873  
2874  
2875  /**
2876   * Path to directory
2877   *
2878   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2879   */
2880  class admin_setting_configfile extends admin_setting_configtext {
2881      /**
2882       * Constructor
2883       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2884       * @param string $visiblename localised
2885       * @param string $description long localised info
2886       * @param string $defaultdirectory default directory location
2887       */
2888      public function __construct($name, $visiblename, $description, $defaultdirectory) {
2889          parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2890      }
2891  
2892      /**
2893       * Returns XHTML for the field
2894       *
2895       * Returns XHTML for the field and also checks whether the file
2896       * specified in $data exists using file_exists()
2897       *
2898       * @param string $data File name and path to use in value attr
2899       * @param string $query
2900       * @return string XHTML field
2901       */
2902      public function output_html($data, $query='') {
2903          global $CFG, $OUTPUT;
2904  
2905          $default = $this->get_defaultsetting();
2906          $context = (object) [
2907              'id' => $this->get_id(),
2908              'name' => $this->get_full_name(),
2909              'size' => $this->size,
2910              'value' => $data,
2911              'showvalidity' => !empty($data),
2912              'valid' => $data && file_exists($data),
2913              'readonly' => !empty($CFG->preventexecpath) || $this->is_readonly(),
2914              'forceltr' => $this->get_force_ltr(),
2915          ];
2916  
2917          if ($context->readonly) {
2918              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2919          }
2920  
2921          $element = $OUTPUT->render_from_template('core_admin/setting_configfile', $context);
2922  
2923          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2924      }
2925  
2926      /**
2927       * Checks if execpatch has been disabled in config.php
2928       */
2929      public function write_setting($data) {
2930          global $CFG;
2931          if (!empty($CFG->preventexecpath)) {
2932              if ($this->get_setting() === null) {
2933                  // Use default during installation.
2934                  $data = $this->get_defaultsetting();
2935                  if ($data === null) {
2936                      $data = '';
2937                  }
2938              } else {
2939                  return '';
2940              }
2941          }
2942          return parent::write_setting($data);
2943      }
2944  
2945  }
2946  
2947  
2948  /**
2949   * Path to executable file
2950   *
2951   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2952   */
2953  class admin_setting_configexecutable extends admin_setting_configfile {
2954  
2955      /**
2956       * Returns an XHTML field
2957       *
2958       * @param string $data This is the value for the field
2959       * @param string $query
2960       * @return string XHTML field
2961       */
2962      public function output_html($data, $query='') {
2963          global $CFG, $OUTPUT;
2964          $default = $this->get_defaultsetting();
2965          require_once("$CFG->libdir/filelib.php");
2966  
2967          $context = (object) [
2968              'id' => $this->get_id(),
2969              'name' => $this->get_full_name(),
2970              'size' => $this->size,
2971              'value' => $data,
2972              'showvalidity' => !empty($data),
2973              'valid' => $data && file_exists($data) && !is_dir($data) && file_is_executable($data),
2974              'readonly' => !empty($CFG->preventexecpath),
2975              'forceltr' => $this->get_force_ltr()
2976          ];
2977  
2978          if (!empty($CFG->preventexecpath)) {
2979              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2980          }
2981  
2982          $element = $OUTPUT->render_from_template('core_admin/setting_configexecutable', $context);
2983  
2984          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2985      }
2986  }
2987  
2988  
2989  /**
2990   * Path to directory
2991   *
2992   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2993   */
2994  class admin_setting_configdirectory extends admin_setting_configfile {
2995  
2996      /**
2997       * Returns an XHTML field
2998       *
2999       * @param string $data This is the value for the field
3000       * @param string $query
3001       * @return string XHTML
3002       */
3003      public function output_html($data, $query='') {
3004          global $CFG, $OUTPUT;
3005          $default = $this->get_defaultsetting();
3006  
3007          $context = (object) [
3008              'id' => $this->get_id(),
3009              'name' => $this->get_full_name(),
3010              'size' => $this->size,
3011              'value' => $data,
3012              'showvalidity' => !empty($data),
3013              'valid' => $data && file_exists($data) && is_dir($data),
3014              'readonly' => !empty($CFG->preventexecpath),
3015              'forceltr' => $this->get_force_ltr()
3016          ];
3017  
3018          if (!empty($CFG->preventexecpath)) {
3019              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
3020          }
3021  
3022          $element = $OUTPUT->render_from_template('core_admin/setting_configdirectory', $context);
3023  
3024          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
3025      }
3026  }
3027  
3028  
3029  /**
3030   * Checkbox
3031   *
3032   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3033   */
3034  class admin_setting_configcheckbox extends admin_setting {
3035      /** @var string Value used when checked */
3036      public $yes;
3037      /** @var string Value used when not checked */
3038      public $no;
3039  
3040      /**
3041       * Constructor
3042       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3043       * @param string $visiblename localised
3044       * @param string $description long localised info
3045       * @param string $defaultsetting
3046       * @param string $yes value used when checked
3047       * @param string $no value used when not checked
3048       */
3049      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
3050          parent::__construct($name, $visiblename, $description, $defaultsetting);
3051          $this->yes = (string)$yes;
3052          $this->no  = (string)$no;
3053      }
3054  
3055      /**
3056       * Retrieves the current setting using the objects name
3057       *
3058       * @return string
3059       */
3060      public function get_setting() {
3061          return $this->config_read($this->name);
3062      }
3063  
3064      /**
3065       * Sets the value for the setting
3066       *
3067       * Sets the value for the setting to either the yes or no values
3068       * of the object by comparing $data to yes
3069       *
3070       * @param mixed $data Gets converted to str for comparison against yes value
3071       * @return string empty string or error
3072       */
3073      public function write_setting($data) {
3074          if ((string)$data === $this->yes) { // convert to strings before comparison
3075              $data = $this->yes;
3076          } else {
3077              $data = $this->no;
3078          }
3079          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3080      }
3081  
3082      /**
3083       * Returns an XHTML checkbox field
3084       *
3085       * @param string $data If $data matches yes then checkbox is checked
3086       * @param string $query
3087       * @return string XHTML field
3088       */
3089      public function output_html($data, $query='') {
3090          global $OUTPUT;
3091  
3092          $context = (object) [
3093              'id' => $this->get_id(),
3094              'name' => $this->get_full_name(),
3095              'no' => $this->no,
3096              'value' => $this->yes,
3097              'checked' => (string) $data === $this->yes,
3098              'readonly' => $this->is_readonly(),
3099          ];
3100  
3101          $default = $this->get_defaultsetting();
3102          if (!is_null($default)) {
3103              if ((string)$default === $this->yes) {
3104                  $defaultinfo = get_string('checkboxyes', 'admin');
3105              } else {
3106                  $defaultinfo = get_string('checkboxno', 'admin');
3107              }
3108          } else {
3109              $defaultinfo = NULL;
3110          }
3111  
3112          $element = $OUTPUT->render_from_template('core_admin/setting_configcheckbox', $context);
3113  
3114          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3115      }
3116  }
3117  
3118  
3119  /**
3120   * Multiple checkboxes, each represents different value, stored in csv format
3121   *
3122   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3123   */
3124  class admin_setting_configmulticheckbox extends admin_setting {
3125      /** @var array Array of choices value=>label */
3126      public $choices;
3127      /** @var callable|null Loader function for choices */
3128      protected $choiceloader = null;
3129  
3130      /**
3131       * Constructor: uses parent::__construct
3132       *
3133       * The $choices parameter may be either an array of $value => $label format,
3134       * e.g. [1 => get_string('yes')], or a callback function which takes no parameters and
3135       * returns an array in that format.
3136       *
3137       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3138       * @param string $visiblename localised
3139       * @param string $description long localised info
3140       * @param array $defaultsetting array of selected
3141       * @param array|callable $choices array of $value => $label for each checkbox, or a callback
3142       */
3143      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3144          if (is_array($choices)) {
3145              $this->choices = $choices;
3146          }
3147          if (is_callable($choices)) {
3148              $this->choiceloader = $choices;
3149          }
3150          parent::__construct($name, $visiblename, $description, $defaultsetting);
3151      }
3152  
3153      /**
3154       * This function may be used in ancestors for lazy loading of choices
3155       *
3156       * Override this method if loading of choices is expensive, such
3157       * as when it requires multiple db requests.
3158       *
3159       * @return bool true if loaded, false if error
3160       */
3161      public function load_choices() {
3162          if ($this->choiceloader) {
3163              if (!is_array($this->choices)) {
3164                  $this->choices = call_user_func($this->choiceloader);
3165              }
3166          }
3167          return true;
3168      }
3169  
3170      /**
3171       * Is setting related to query text - used when searching
3172       *
3173       * @param string $query
3174       * @return bool true on related, false on not or failure
3175       */
3176      public function is_related($query) {
3177          if (!$this->load_choices() or empty($this->choices)) {
3178              return false;
3179          }
3180          if (parent::is_related($query)) {
3181              return true;
3182          }
3183  
3184          foreach ($this->choices as $desc) {
3185              if (strpos(core_text::strtolower($desc), $query) !== false) {
3186                  return true;
3187              }
3188          }
3189          return false;
3190      }
3191  
3192      /**
3193       * Returns the current setting if it is set
3194       *
3195       * @return mixed null if null, else an array
3196       */
3197      public function get_setting() {
3198          $result = $this->config_read($this->name);
3199  
3200          if (is_null($result)) {
3201              return NULL;
3202          }
3203          if ($result === '') {
3204              return array();
3205          }
3206          $enabled = explode(',', $result);
3207          $setting = array();
3208          foreach ($enabled as $option) {
3209              $setting[$option] = 1;
3210          }
3211          return $setting;
3212      }
3213  
3214      /**
3215       * Saves the setting(s) provided in $data
3216       *
3217       * @param array $data An array of data, if not array returns empty str
3218       * @return mixed empty string on useless data or bool true=success, false=failed
3219       */
3220      public function write_setting($data) {
3221          if (!is_array($data)) {
3222              return ''; // ignore it
3223          }
3224          if (!$this->load_choices() or empty($this->choices)) {
3225              return '';
3226          }
3227          unset($data['xxxxx']);
3228          $result = array();
3229          foreach ($data as $key => $value) {
3230              if ($value and array_key_exists($key, $this->choices)) {
3231                  $result[] = $key;
3232              }
3233          }
3234          return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
3235      }
3236  
3237      /**
3238       * Returns XHTML field(s) as required by choices
3239       *
3240       * Relies on data being an array should data ever be another valid vartype with
3241       * acceptable value this may cause a warning/error
3242       * if (!is_array($data)) would fix the problem
3243       *
3244       * @todo Add vartype handling to ensure $data is an array
3245       *
3246       * @param array $data An array of checked values
3247       * @param string $query
3248       * @return string XHTML field
3249       */
3250      public function output_html($data, $query='') {
3251          global $OUTPUT;
3252  
3253          if (!$this->load_choices() or empty($this->choices)) {
3254              return '';
3255          }
3256  
3257          $default = $this->get_defaultsetting();
3258          if (is_null($default)) {
3259              $default = array();
3260          }
3261          if (is_null($data)) {
3262              $data = array();
3263          }
3264  
3265          $context = (object) [
3266              'id' => $this->get_id(),
3267              'name' => $this->get_full_name(),
3268              'readonly' => $this->is_readonly(),
3269          ];
3270  
3271          $options = array();
3272          $defaults = array();
3273          foreach ($this->choices as $key => $description) {
3274              if (!empty($default[$key])) {
3275                  $defaults[] = $description;
3276              }
3277  
3278              $options[] = [
3279                  'key' => $key,
3280                  'checked' => !empty($data[$key]),
3281                  'label' => highlightfast($query, $description)
3282              ];
3283          }
3284  
3285          if (is_null($default)) {
3286              $defaultinfo = null;
3287          } else if (!empty($defaults)) {
3288              $defaultinfo = implode(', ', $defaults);
3289          } else {
3290              $defaultinfo = get_string('none');
3291          }
3292  
3293          $context->options = $options;
3294          $context->hasoptions = !empty($options);
3295  
3296          $element = $OUTPUT->render_from_template('core_admin/setting_configmulticheckbox', $context);
3297  
3298          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', $defaultinfo, $query);
3299  
3300      }
3301  }
3302  
3303  
3304  /**
3305   * Multiple checkboxes 2, value stored as string 00101011
3306   *
3307   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3308   */
3309  class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
3310  
3311      /**
3312       * Returns the setting if set
3313       *
3314       * @return mixed null if not set, else an array of set settings
3315       */
3316      public function get_setting() {
3317          $result = $this->config_read($this->name);
3318          if (is_null($result)) {
3319              return NULL;
3320          }
3321          if (!$this->load_choices()) {
3322              return NULL;
3323          }
3324          $result = str_pad($result, count($this->choices), '0');
3325          $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
3326          $setting = array();
3327          foreach ($this->choices as $key=>$unused) {
3328              $value = array_shift($result);
3329              if ($value) {
3330                  $setting[$key] = 1;
3331              }
3332          }
3333          return $setting;
3334      }
3335  
3336      /**
3337       * Save setting(s) provided in $data param
3338       *
3339       * @param array $data An array of settings to save
3340       * @return mixed empty string for bad data or bool true=>success, false=>error
3341       */
3342      public function write_setting($data) {
3343          if (!is_array($data)) {
3344              return ''; // ignore it
3345          }
3346          if (!$this->load_choices() or empty($this->choices)) {
3347              return '';
3348          }
3349          $result = '';
3350          foreach ($this->choices as $key=>$unused) {
3351              if (!empty($data[$key])) {
3352                  $result .= '1';
3353              } else {
3354                  $result .= '0';
3355              }
3356          }
3357          return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
3358      }
3359  }
3360  
3361  
3362  /**
3363   * Select one value from list
3364   *
3365   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3366   */
3367  class admin_setting_configselect extends admin_setting {
3368      /** @var array Array of choices value=>label */
3369      public $choices;
3370      /** @var array Array of choices grouped using optgroups */
3371      public $optgroups;
3372      /** @var callable|null Loader function for choices */
3373      protected $choiceloader = null;
3374      /** @var callable|null Validation function */
3375      protected $validatefunction = null;
3376  
3377      /**
3378       * Constructor.
3379       *
3380       * If you want to lazy-load the choices, pass a callback function that returns a choice
3381       * array for the $choices parameter.
3382       *
3383       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3384       * @param string $visiblename localised
3385       * @param string $description long localised info
3386       * @param string|int $defaultsetting
3387       * @param array|callable|null $choices array of $value=>$label for each selection, or callback
3388       */
3389      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3390          // Look for optgroup and single options.
3391          if (is_array($choices)) {
3392              $this->choices = [];
3393              foreach ($choices as $key => $val) {
3394                  if (is_array($val)) {
3395                      $this->optgroups[$key] = $val;
3396                      $this->choices = array_merge($this->choices, $val);
3397                  } else {
3398                      $this->choices[$key] = $val;
3399                  }
3400              }
3401          }
3402          if (is_callable($choices)) {
3403              $this->choiceloader = $choices;
3404          }
3405  
3406          parent::__construct($name, $visiblename, $description, $defaultsetting);
3407      }
3408  
3409      /**
3410       * Sets a validate function.
3411       *
3412       * The callback will be passed one parameter, the new setting value, and should return either
3413       * an empty string '' if the value is OK, or an error message if not.
3414       *
3415       * @param callable|null $validatefunction Validate function or null to clear
3416       * @since Moodle 3.10
3417       */
3418      public function set_validate_function(?callable $validatefunction = null) {
3419          $this->validatefunction = $validatefunction;
3420      }
3421  
3422      /**
3423       * This function may be used in ancestors for lazy loading of choices
3424       *
3425       * Override this method if loading of choices is expensive, such
3426       * as when it requires multiple db requests.
3427       *
3428       * @return bool true if loaded, false if error
3429       */
3430      public function load_choices() {
3431          if ($this->choiceloader) {
3432              if (!is_array($this->choices)) {
3433                  $this->choices = call_user_func($this->choiceloader);
3434              }
3435              return true;
3436          }
3437          return true;
3438      }
3439  
3440      /**
3441       * Check if this is $query is related to a choice
3442       *
3443       * @param string $query
3444       * @return bool true if related, false if not
3445       */
3446      public function is_related($query) {
3447          if (parent::is_related($query)) {
3448              return true;
3449          }
3450          if (!$this->load_choices()) {
3451              return false;
3452          }
3453          foreach ($this->choices as $key=>$value) {
3454              if (strpos(core_text::strtolower($key), $query) !== false) {
3455                  return true;
3456              }
3457              if (strpos(core_text::strtolower($value), $query) !== false) {
3458                  return true;
3459              }
3460          }
3461          return false;
3462      }
3463  
3464      /**
3465       * Return the setting
3466       *
3467       * @return mixed returns config if successful else null
3468       */
3469      public function get_setting() {
3470          return $this->config_read($this->name);
3471      }
3472  
3473      /**
3474       * Save a setting
3475       *
3476       * @param string $data
3477       * @return string empty of error string
3478       */
3479      public function write_setting($data) {
3480          if (!$this->load_choices() or empty($this->choices)) {
3481              return '';
3482          }
3483          if (!array_key_exists($data, $this->choices)) {
3484              return ''; // ignore it
3485          }
3486  
3487          // Validate the new setting.
3488          $error = $this->validate_setting($data);
3489          if ($error) {
3490              return $error;
3491          }
3492  
3493          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3494      }
3495  
3496      /**
3497       * Validate the setting. This uses the callback function if provided; subclasses could override
3498       * to carry out validation directly in the class.
3499       *
3500       * @param string $data New value being set
3501       * @return string Empty string if valid, or error message text
3502       * @since Moodle 3.10
3503       */
3504      protected function validate_setting(string $data): string {
3505          // If validation function is specified, call it now.
3506          if ($this->validatefunction) {
3507              return call_user_func($this->validatefunction, $data);
3508          } else {
3509              return '';
3510          }
3511      }
3512  
3513      /**
3514       * Returns XHTML select field
3515       *
3516       * Ensure the options are loaded, and generate the XHTML for the select
3517       * element and any warning message. Separating this out from output_html
3518       * makes it easier to subclass this class.
3519       *
3520       * @param string $data the option to show as selected.
3521       * @param string $current the currently selected option in the database, null if none.
3522       * @param string $default the default selected option.
3523       * @return array the HTML for the select element, and a warning message.
3524       * @deprecated since Moodle 3.2
3525       */
3526      public function output_select_html($data, $current, $default, $extraname = '') {
3527          debugging('The method admin_setting_configselect::output_select_html is depreacted, do not use any more.', DEBUG_DEVELOPER);
3528      }
3529  
3530      /**
3531       * Returns XHTML select field and wrapping div(s)
3532       *
3533       * @see output_select_html()
3534       *
3535       * @param string $data the option to show as selected
3536       * @param string $query
3537       * @return string XHTML field and wrapping div
3538       */
3539      public function output_html($data, $query='') {
3540          global $OUTPUT;
3541  
3542          $default = $this->get_defaultsetting();
3543          $current = $this->get_setting();
3544  
3545          if (!$this->load_choices() || empty($this->choices)) {
3546              return '';
3547          }
3548  
3549          $context = (object) [
3550              'id' => $this->get_id(),
3551              'name' => $this->get_full_name(),
3552          ];
3553  
3554          if (!is_null($default) && array_key_exists($default, $this->choices)) {
3555              $defaultinfo = $this->choices[$default];
3556          } else {
3557              $defaultinfo = NULL;
3558          }
3559  
3560          // Warnings.
3561          $warning = '';
3562          if ($current === null) {
3563              // First run.
3564          } else if (empty($current) && (array_key_exists('', $this->choices) || array_key_exists(0, $this->choices))) {
3565              // No warning.
3566          } else if (!array_key_exists($current, $this->choices)) {
3567              $warning = get_string('warningcurrentsetting', 'admin', $current);
3568              if (!is_null($default) && $data == $current) {
3569                  $data = $default; // Use default instead of first value when showing the form.
3570              }
3571          }
3572  
3573          $options = [];
3574          $template = 'core_admin/setting_configselect';
3575  
3576          if (!empty($this->optgroups)) {
3577              $optgroups = [];
3578              foreach ($this->optgroups as $label => $choices) {
3579                  $optgroup = array('label' => $label, 'options' => []);
3580                  foreach ($choices as $value => $name) {
3581                      $optgroup['options'][] = [
3582                          'value' => $value,
3583                          'name' => $name,
3584                          'selected' => (string) $value == $data
3585                      ];
3586                      unset($this->choices[$value]);
3587                  }
3588                  $optgroups[] = $optgroup;
3589              }
3590              $context->options = $options;
3591              $context->optgroups = $optgroups;
3592              $template = 'core_admin/setting_configselect_optgroup';
3593          }
3594  
3595          foreach ($this->choices as $value => $name) {
3596              $options[] = [
3597                  'value' => $value,
3598                  'name' => $name,
3599                  'selected' => (string) $value == $data
3600              ];
3601          }
3602          $context->options = $options;
3603          $context->readonly = $this->is_readonly();
3604  
3605          $element = $OUTPUT->render_from_template($template, $context);
3606  
3607          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, $warning, $defaultinfo, $query);
3608      }
3609  }
3610  
3611  /**
3612   * Select multiple items from list
3613   *
3614   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3615   */
3616  class admin_setting_configmultiselect extends admin_setting_configselect {
3617      /**
3618       * Constructor
3619       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3620       * @param string $visiblename localised
3621       * @param string $description long localised info
3622       * @param array $defaultsetting array of selected items
3623       * @param array $choices array of $value=>$label for each list item
3624       */
3625      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3626          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
3627      }
3628  
3629      /**
3630       * Returns the select setting(s)
3631       *
3632       * @return mixed null or array. Null if no settings else array of setting(s)
3633       */
3634      public function get_setting() {
3635          $result = $this->config_read($this->name);
3636          if (is_null($result)) {
3637              return NULL;
3638          }
3639          if ($result === '') {
3640              return array();
3641          }
3642          return explode(',', $result);
3643      }
3644  
3645      /**
3646       * Saves setting(s) provided through $data
3647       *
3648       * Potential bug in the works should anyone call with this function
3649       * using a vartype that is not an array
3650       *
3651       * @param array $data
3652       */
3653      public function write_setting($data) {
3654          if (!is_array($data)) {
3655              return ''; //ignore it
3656          }
3657          if (!$this->load_choices() or empty($this->choices)) {
3658              return '';
3659          }
3660  
3661          unset($data['xxxxx']);
3662  
3663          $save = array();
3664          foreach ($data as $value) {
3665              if (!array_key_exists($value, $this->choices)) {
3666                  continue; // ignore it
3667              }
3668              $save[] = $value;
3669          }
3670  
3671          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3672      }
3673  
3674      /**
3675       * Is setting related to query text - used when searching
3676       *
3677       * @param string $query
3678       * @return bool true if related, false if not
3679       */
3680      public function is_related($query) {
3681          if (!$this->load_choices() or empty($this->choices)) {
3682              return false;
3683          }
3684          if (parent::is_related($query)) {
3685              return true;
3686          }
3687  
3688          foreach ($this->choices as $desc) {
3689              if (strpos(core_text::strtolower($desc), $query) !== false) {
3690                  return true;
3691              }
3692          }
3693          return false;
3694      }
3695  
3696      /**
3697       * Returns XHTML multi-select field
3698       *
3699       * @todo Add vartype handling to ensure $data is an array
3700       * @param array $data Array of values to select by default
3701       * @param string $query
3702       * @return string XHTML multi-select field
3703       */
3704      public function output_html($data, $query='') {
3705          global $OUTPUT;
3706  
3707          if (!$this->load_choices() or empty($this->choices)) {
3708              return '';
3709          }
3710  
3711          $default = $this->get_defaultsetting();
3712          if (is_null($default)) {
3713              $default = array();
3714          }
3715          if (is_null($data)) {
3716              $data = array();
3717          }
3718  
3719          $context = (object) [
3720              'id' => $this->get_id(),
3721              'name' => $this->get_full_name(),
3722              'size' => min(10, count($this->choices))
3723          ];
3724  
3725          $defaults = [];
3726          $options = [];
3727          $template = 'core_admin/setting_configmultiselect';
3728  
3729          if (!empty($this->optgroups)) {
3730              $optgroups = [];
3731              foreach ($this->optgroups as $label => $choices) {
3732                  $optgroup = array('label' => $label, 'options' => []);
3733                  foreach ($choices as $value => $name) {
3734                      if (in_array($value, $default)) {
3735                          $defaults[] = $name;
3736                      }
3737                      $optgroup['options'][] = [
3738                          'value' => $value,
3739                          'name' => $name,
3740                          'selected' => in_array($value, $data)
3741                      ];
3742                      unset($this->choices[$value]);
3743                  }
3744                  $optgroups[] = $optgroup;
3745              }
3746              $context->optgroups = $optgroups;
3747              $template = 'core_admin/setting_configmultiselect_optgroup';
3748          }
3749  
3750          foreach ($this->choices as $value => $name) {
3751              if (in_array($value, $default)) {
3752                  $defaults[] = $name;
3753              }
3754              $options[] = [
3755                  'value' => $value,
3756                  'name' => $name,
3757                  'selected' => in_array($value, $data)
3758              ];
3759          }
3760          $context->options = $options;
3761          $context->readonly = $this->is_readonly();
3762  
3763          if (is_null($default)) {
3764              $defaultinfo = NULL;
3765          } if (!empty($defaults)) {
3766              $defaultinfo = implode(', ', $defaults);
3767          } else {
3768              $defaultinfo = get_string('none');
3769          }
3770  
3771          $element = $OUTPUT->render_from_template($template, $context);
3772  
3773          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3774      }
3775  }
3776  
3777  /**
3778   * Time selector
3779   *
3780   * This is a liiitle bit messy. we're using two selects, but we're returning
3781   * them as an array named after $name (so we only use $name2 internally for the setting)
3782   *
3783   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3784   */
3785  class admin_setting_configtime extends admin_setting {
3786      /** @var string Used for setting second select (minutes) */
3787      public $name2;
3788  
3789      /**
3790       * Constructor
3791       * @param string $hoursname setting for hours
3792       * @param string $minutesname setting for hours
3793       * @param string $visiblename localised
3794       * @param string $description long localised info
3795       * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3796       */
3797      public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
3798          $this->name2 = $minutesname;
3799          parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
3800      }
3801  
3802      /**
3803       * Get the selected time
3804       *
3805       * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
3806       */
3807      public function get_setting() {
3808          $result1 = $this->config_read($this->name);
3809          $result2 = $this->config_read($this->name2);
3810          if (is_null($result1) or is_null($result2)) {
3811              return NULL;
3812          }
3813  
3814          return array('h' => $result1, 'm' => $result2);
3815      }
3816  
3817      /**
3818       * Store the time (hours and minutes)
3819       *
3820       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3821       * @return bool true if success, false if not
3822       */
3823      public function write_setting($data) {
3824          if (!is_array($data)) {
3825              return '';
3826          }
3827  
3828          $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3829          return ($result ? '' : get_string('errorsetting', 'admin'));
3830      }
3831  
3832      /**
3833       * Returns XHTML time select fields
3834       *
3835       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3836       * @param string $query
3837       * @return string XHTML time select fields and wrapping div(s)
3838       */
3839      public function output_html($data, $query='') {
3840          global $OUTPUT;
3841  
3842          $default = $this->get_defaultsetting();
3843          if (is_array($default)) {
3844              $defaultinfo = $default['h'].':'.$default['m'];
3845          } else {
3846              $defaultinfo = NULL;
3847          }
3848  
3849          $context = (object) [
3850              'id' => $this->get_id(),
3851              'name' => $this->get_full_name(),
3852              'readonly' => $this->is_readonly(),
3853              'hours' => array_map(function($i) use ($data) {
3854                  return [
3855                      'value' => $i,
3856                      'name' => $i,
3857                      'selected' => $i == $data['h']
3858                  ];
3859              }, range(0, 23)),
3860              'minutes' => array_map(function($i) use ($data) {
3861                  return [
3862                      'value' => $i,
3863                      'name' => $i,
3864                      'selected' => $i == $data['m']
3865                  ];
3866              }, range(0, 59, 5))
3867          ];
3868  
3869          $element = $OUTPUT->render_from_template('core_admin/setting_configtime', $context);
3870  
3871          return format_admin_setting($this, $this->visiblename, $element, $this->description,
3872              $this->get_id() . 'h', '', $defaultinfo, $query);
3873      }
3874  
3875  }
3876  
3877  
3878  /**
3879   * Seconds duration setting.
3880   *
3881   * @copyright 2012 Petr Skoda (http://skodak.org)
3882   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3883   */
3884  class admin_setting_configduration extends admin_setting {
3885  
3886      /** @var int default duration unit */
3887      protected $defaultunit;
3888      /** @var callable|null Validation function */
3889      protected $validatefunction = null;
3890  
3891      /**
3892       * Constructor
3893       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3894       *                     or 'myplugin/mysetting' for ones in config_plugins.
3895       * @param string $visiblename localised name
3896       * @param string $description localised long description
3897       * @param mixed $defaultsetting string or array depending on implementation
3898       * @param int $defaultunit - day, week, etc. (in seconds)
3899       */
3900      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3901          if (is_number($defaultsetting)) {
3902              $defaultsetting = self::parse_seconds($defaultsetting);
3903          }
3904          $units = self::get_units();
3905          if (isset($units[$defaultunit])) {
3906              $this->defaultunit = $defaultunit;
3907          } else {
3908              $this->defaultunit = 86400;
3909          }
3910          parent::__construct($name, $visiblename, $description, $defaultsetting);
3911      }
3912  
3913      /**
3914       * Sets a validate function.
3915       *
3916       * The callback will be passed one parameter, the new setting value, and should return either
3917       * an empty string '' if the value is OK, or an error message if not.
3918       *
3919       * @param callable|null $validatefunction Validate function or null to clear
3920       * @since Moodle 3.10
3921       */
3922      public function set_validate_function(?callable $validatefunction = null) {
3923          $this->validatefunction = $validatefunction;
3924      }
3925  
3926      /**
3927       * Validate the setting. This uses the callback function if provided; subclasses could override
3928       * to carry out validation directly in the class.
3929       *
3930       * @param int $data New value being set
3931       * @return string Empty string if valid, or error message text
3932       * @since Moodle 3.10
3933       */
3934      protected function validate_setting(int $data): string {
3935          // If validation function is specified, call it now.
3936          if ($this->validatefunction) {
3937              return call_user_func($this->validatefunction, $data);
3938          } else {
3939              if ($data < 0) {
3940                  return get_string('errorsetting', 'admin');
3941              }
3942              return '';
3943          }
3944      }
3945  
3946      /**
3947       * Returns selectable units.
3948       * @static
3949       * @return array
3950       */
3951      protected static function get_units() {
3952          return array(
3953              604800 => get_string('weeks'),
3954              86400 => get_string('days'),
3955              3600 => get_string('hours'),
3956              60 => get_string('minutes'),
3957              1 => get_string('seconds'),
3958          );
3959      }
3960  
3961      /**
3962       * Converts seconds to some more user friendly string.
3963       * @static
3964       * @param int $seconds
3965       * @return string
3966       */
3967      protected static function get_duration_text($seconds) {
3968          if (empty($seconds)) {
3969              return get_string('none');
3970          }
3971          $data = self::parse_seconds($seconds);
3972          switch ($data['u']) {
3973              case (60*60*24*7):
3974                  return get_string('numweeks', '', $data['v']);
3975              case (60*60*24):
3976                  return get_string('numdays', '', $data['v']);
3977              case (60*60):
3978                  return get_string('numhours', '', $data['v']);
3979              case (60):
3980                  return get_string('numminutes', '', $data['v']);
3981              default:
3982                  return get_string('numseconds', '', $data['v']*$data['u']);
3983          }
3984      }
3985  
3986      /**
3987       * Finds suitable units for given duration.
3988       * @static
3989       * @param int $seconds
3990       * @return array
3991       */
3992      protected static function parse_seconds($seconds) {
3993          foreach (self::get_units() as $unit => $unused) {
3994              if ($seconds % $unit === 0) {
3995                  return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
3996              }
3997          }
3998          return array('v'=>(int)$seconds, 'u'=>1);
3999      }
4000  
4001      /**
4002       * Get the selected duration as array.
4003       *
4004       * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
4005       */
4006      public function get_setting() {
4007          $seconds = $this->config_read($this->name);
4008          if (is_null($seconds)) {
4009              return null;
4010          }
4011  
4012          return self::parse_seconds($seconds);
4013      }
4014  
4015      /**
4016       * Store the duration as seconds.
4017       *
4018       * @param array $data Must be form 'h'=>xx, 'm'=>xx
4019       * @return bool true if success, false if not
4020       */
4021      public function write_setting($data) {
4022          if (!is_array($data)) {
4023              return '';
4024          }
4025  
4026          $unit = (int)$data['u'];
4027          $value = (int)$data['v'];
4028          $seconds = $value * $unit;
4029  
4030          // Validate the new setting.
4031          $error = $this->validate_setting($seconds);
4032          if ($error) {
4033              return $error;
4034          }
4035  
4036          $result = $this->config_write($this->name, $seconds);
4037          return ($result ? '' : get_string('errorsetting', 'admin'));
4038      }
4039  
4040      /**
4041       * Returns duration text+select fields.
4042       *
4043       * @param array $data Must be form 'v'=>xx, 'u'=>xx
4044       * @param string $query
4045       * @return string duration text+select fields and wrapping div(s)
4046       */
4047      public function output_html($data, $query='') {
4048          global $OUTPUT;
4049  
4050          $default = $this->get_defaultsetting();
4051          if (is_number($default)) {
4052              $defaultinfo = self::get_duration_text($default);
4053          } else if (is_array($default)) {
4054              $defaultinfo = self::get_duration_text($default['v']*$default['u']);
4055          } else {
4056              $defaultinfo = null;
4057          }
4058  
4059          $inputid = $this->get_id() . 'v';
4060          $units = self::get_units();
4061          $defaultunit = $this->defaultunit;
4062  
4063          $context = (object) [
4064              'id' => $this->get_id(),
4065              'name' => $this->get_full_name(),
4066              'value' => $data['v'] ?? '',
4067              'readonly' => $this->is_readonly(),
4068              'options' => array_map(function($unit) use ($units, $data, $defaultunit) {
4069                  return [
4070                      'value' => $unit,
4071                      'name' => $units[$unit],
4072                      'selected' => isset($data) && (($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u'])
4073                  ];
4074              }, array_keys($units))
4075          ];
4076  
4077          $element = $OUTPUT->render_from_template('core_admin/setting_configduration', $context);
4078  
4079          return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
4080      }
4081  }
4082  
4083  
4084  /**
4085   * Seconds duration setting with an advanced checkbox, that controls a additional
4086   * $name.'_adv' setting.
4087   *
4088   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4089   * @copyright 2014 The Open University
4090   */
4091  class admin_setting_configduration_with_advanced extends admin_setting_configduration {
4092      /**
4093       * Constructor
4094       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
4095       *                     or 'myplugin/mysetting' for ones in config_plugins.
4096       * @param string $visiblename localised name
4097       * @param string $description localised long description
4098       * @param array  $defaultsetting array of int value, and bool whether it is
4099       *                     is advanced by default.
4100       * @param int $defaultunit - day, week, etc. (in seconds)
4101       */
4102      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
4103          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $defaultunit);
4104          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
4105      }
4106  }
4107  
4108  
4109  /**
4110   * Used to validate a textarea used for ip addresses
4111   *
4112   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4113   * @copyright 2011 Petr Skoda (http://skodak.org)
4114   */
4115  class admin_setting_configiplist extends admin_setting_configtextarea {
4116  
4117      /**
4118       * Validate the contents of the textarea as IP addresses
4119       *
4120       * Used to validate a new line separated list of IP addresses collected from
4121       * a textarea control
4122       *
4123       * @param string $data A list of IP Addresses separated by new lines
4124       * @return mixed bool true for success or string:error on failure
4125       */
4126      public function validate($data) {
4127          if(!empty($data)) {
4128              $lines = explode("\n", $data);
4129          } else {
4130              return true;
4131          }
4132          $result = true;
4133          $badips = array();
4134          foreach ($lines as $line) {
4135              $tokens = explode('#', $line);
4136              $ip = trim($tokens[0]);
4137              if (empty($ip)) {
4138                  continue;
4139              }
4140              if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
4141                  preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
4142                  preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
4143              } else {
4144                  $result = false;
4145                  $badips[] = $ip;
4146              }
4147          }
4148          if($result) {
4149              return true;
4150          } else {
4151              return get_string('validateiperror', 'admin', join(', ', $badips));
4152          }
4153      }
4154  }
4155  
4156  /**
4157   * Used to validate a textarea used for domain names, wildcard domain names and IP addresses/ranges (both IPv4 and IPv6 format).
4158   *
4159   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4160   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4161   */
4162  class admin_setting_configmixedhostiplist extends admin_setting_configtextarea {
4163  
4164      /**
4165       * Validate the contents of the textarea as either IP addresses, domain name or wildcard domain name (RFC 4592).
4166       * Used to validate a new line separated list of entries collected from a textarea control.
4167       *
4168       * This setting provides support for internationalised domain names (IDNs), however, such UTF-8 names will be converted to
4169       * their ascii-compatible encoding (punycode) on save, and converted back to their UTF-8 representation when fetched
4170       * via the get_setting() method, which has been overriden.
4171       *
4172       * @param string $data A list of FQDNs, DNS wildcard format domains, and IP addresses, separated by new lines.
4173       * @return mixed bool true for success or string:error on failure
4174       */
4175      public function validate($data) {
4176          if (empty($data)) {
4177              return true;
4178          }
4179          $entries = explode("\n", $data);
4180          $badentries = [];
4181  
4182          foreach ($entries as $key => $entry) {
4183              $entry = trim($entry);
4184              if (empty($entry)) {
4185                  return get_string('validateemptylineerror', 'admin');
4186              }
4187  
4188              // Validate each string entry against the supported formats.
4189              if (\core\ip_utils::is_ip_address($entry) || \core\ip_utils::is_ipv6_range($entry)
4190                      || \core\ip_utils::is_ipv4_range($entry) || \core\ip_utils::is_domain_name($entry)
4191                      || \core\ip_utils::is_domain_matching_pattern($entry)) {
4192                  continue;
4193              }
4194  
4195              // Otherwise, the entry is invalid.
4196              $badentries[] = $entry;
4197          }
4198  
4199          if ($badentries) {
4200              return get_string('validateerrorlist', 'admin', join(', ', $badentries));
4201          }
4202          return true;
4203      }
4204  
4205      /**
4206       * Convert any lines containing international domain names (IDNs) to their ascii-compatible encoding (ACE).
4207       *
4208       * @param string $data the setting data, as sent from the web form.
4209       * @return string $data the setting data, with all IDNs converted (using punycode) to their ascii encoded version.
4210       */
4211      protected function ace_encode($data) {
4212          if (empty($data)) {
4213              return $data;
4214          }
4215          $entries = explode("\n", $data);
4216          foreach ($entries as $key => $entry) {
4217              $entry = trim($entry);
4218              // This regex matches any string that has non-ascii character.
4219              if (preg_match('/[^\x00-\x7f]/', $entry)) {
4220                  // If we can convert the unicode string to an idn, do so.
4221                  // Otherwise, leave the original unicode string alone and let the validation function handle it (it will fail).
4222                  $val = idn_to_ascii($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4223                  $entries[$key] = $val ? $val : $entry;
4224              }
4225          }
4226          return implode("\n", $entries);
4227      }
4228  
4229      /**
4230       * Decode any ascii-encoded domain names back to their utf-8 representation for display.
4231       *
4232       * @param string $data the setting data, as found in the database.
4233       * @return string $data the setting data, with all ascii-encoded IDNs decoded back to their utf-8 representation.
4234       */
4235      protected function ace_decode($data) {
4236          $entries = explode("\n", $data);
4237          foreach ($entries as $key => $entry) {
4238              $entry = trim($entry);
4239              if (strpos($entry, 'xn--') !== false) {
4240                  $entries[$key] = idn_to_utf8($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4241              }
4242          }
4243          return implode("\n", $entries);
4244      }
4245  
4246      /**
4247       * Override, providing utf8-decoding for ascii-encoded IDN strings.
4248       *
4249       * @return mixed returns punycode-converted setting string if successful, else null.
4250       */
4251      public function get_setting() {
4252          // Here, we need to decode any ascii-encoded IDNs back to their native, utf-8 representation.
4253          $data = $this->config_read($this->name);
4254          if (function_exists('idn_to_utf8') && !is_null($data)) {
4255              $data = $this->ace_decode($data);
4256          }
4257          return $data;
4258      }
4259  
4260      /**
4261       * Override, providing ascii-encoding for utf8 (native) IDN strings.
4262       *
4263       * @param string $data
4264       * @return string
4265       */
4266      public function write_setting($data) {
4267          if ($this->paramtype === PARAM_INT and $data === '') {
4268              // Do not complain if '' used instead of 0.
4269              $data = 0;
4270          }
4271  
4272          // Try to convert any non-ascii domains to ACE prior to validation - we can't modify anything in validate!
4273          if (function_exists('idn_to_ascii')) {
4274              $data = $this->ace_encode($data);
4275          }
4276  
4277          $validated = $this->validate($data);
4278          if ($validated !== true) {
4279              return $validated;
4280          }
4281          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
4282      }
4283  }
4284  
4285  /**
4286   * Used to validate a textarea used for port numbers.
4287   *
4288   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4289   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4290   */
4291  class admin_setting_configportlist extends admin_setting_configtextarea {
4292  
4293      /**
4294       * Validate the contents of the textarea as port numbers.
4295       * Used to validate a new line separated list of ports collected from a textarea control.
4296       *
4297       * @param string $data A list of ports separated by new lines
4298       * @return mixed bool true for success or string:error on failure
4299       */
4300      public function validate($data) {
4301          if (empty($data)) {
4302              return true;
4303          }
4304          $ports = explode("\n", $data);
4305          $badentries = [];
4306          foreach ($ports as $port) {
4307              $port = trim($port);
4308              if (empty($port)) {
4309                  return get_string('validateemptylineerror', 'admin');
4310              }
4311  
4312              // Is the string a valid integer number?
4313              if (strval(intval($port)) !== $port || intval($port) <= 0) {
4314                  $badentries[] = $port;
4315              }
4316          }
4317          if ($badentries) {
4318              return get_string('validateerrorlist', 'admin', $badentries);
4319          }
4320          return true;
4321      }
4322  }
4323  
4324  
4325  /**
4326   * An admin setting for selecting one or more users who have a capability
4327   * in the system context
4328   *
4329   * An admin setting for selecting one or more users, who have a particular capability
4330   * in the system context. Warning, make sure the list will never be too long. There is
4331   * no paging or searching of this list.
4332   *
4333   * To correctly get a list of users from this config setting, you need to call the
4334   * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
4335   *
4336   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4337   */
4338  class admin_setting_users_with_capability extends admin_setting_configmultiselect {
4339      /** @var string The capabilities name */
4340      protected $capability;
4341      /** @var int include admin users too */
4342      protected $includeadmins;
4343  
4344      /**
4345       * Constructor.
4346       *
4347       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
4348       * @param string $visiblename localised name
4349       * @param string $description localised long description
4350       * @param array $defaultsetting array of usernames
4351       * @param string $capability string capability name.
4352       * @param bool $includeadmins include administrators
4353       */
4354      function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
4355          $this->capability    = $capability;
4356          $this->includeadmins = $includeadmins;
4357          parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
4358      }
4359  
4360      /**
4361       * Load all of the uses who have the capability into choice array
4362       *
4363       * @return bool Always returns true
4364       */
4365      function load_choices() {
4366          if (is_array($this->choices)) {
4367              return true;
4368          }
4369          list($sort, $sortparams) = users_order_by_sql('u');
4370          if (!empty($sortparams)) {
4371              throw new coding_exception('users_order_by_sql returned some query parameters. ' .
4372                      'This is unexpected, and a problem because there is no way to pass these ' .
4373                      'parameters to get_users_by_capability. See MDL-34657.');
4374          }
4375          $userfieldsapi = \core_user\fields::for_name();
4376          $userfields = 'u.id, u.username, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects;
4377          $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort);
4378          $this->choices = array(
4379              '$@NONE@$' => get_string('nobody'),
4380              '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
4381          );
4382          if ($this->includeadmins) {
4383              $admins = get_admins();
4384              foreach ($admins as $user) {
4385                  $this->choices[$user->id] = fullname($user);
4386              }
4387          }
4388          if (is_array($users)) {
4389              foreach ($users as $user) {
4390                  $this->choices[$user->id] = fullname($user);
4391              }
4392          }
4393          return true;
4394      }
4395  
4396      /**
4397       * Returns the default setting for class
4398       *
4399       * @return mixed Array, or string. Empty string if no default
4400       */
4401      public function get_defaultsetting() {
4402          $this->load_choices();
4403          $defaultsetting = parent::get_defaultsetting();
4404          if (empty($defaultsetting)) {
4405              return array('$@NONE@$');
4406          } else if (array_key_exists($defaultsetting, $this->choices)) {
4407                  return $defaultsetting;
4408              } else {
4409                  return '';
4410              }
4411      }
4412  
4413      /**
4414       * Returns the current setting
4415       *
4416       * @return mixed array or string
4417       */
4418      public function get_setting() {
4419          $result = parent::get_setting();
4420          if ($result === null) {
4421              // this is necessary for settings upgrade
4422              return null;
4423          }
4424          if (empty($result)) {
4425              $result = array('$@NONE@$');
4426          }
4427          return $result;
4428      }
4429  
4430      /**
4431       * Save the chosen setting provided as $data
4432       *
4433       * @param array $data
4434       * @return mixed string or array
4435       */
4436      public function write_setting($data) {
4437      // If all is selected, remove any explicit options.
4438          if (in_array('$@ALL@$', $data)) {
4439              $data = array('$@ALL@$');
4440          }
4441          // None never needs to be written to the DB.
4442          if (in_array('$@NONE@$', $data)) {
4443              unset($data[array_search('$@NONE@$', $data)]);
4444          }
4445          return parent::write_setting($data);
4446      }
4447  }
4448  
4449  
4450  /**
4451   * Special checkbox for calendar - resets SESSION vars.
4452   *
4453   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4454   */
4455  class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
4456      /**
4457       * Calls the parent::__construct with default values
4458       *
4459       * name =>  calendar_adminseesall
4460       * visiblename => get_string('adminseesall', 'admin')
4461       * description => get_string('helpadminseesall', 'admin')
4462       * defaultsetting => 0
4463       */
4464      public function __construct() {
4465          parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
4466              get_string('helpadminseesall', 'admin'), '0');
4467      }
4468  
4469      /**
4470       * Stores the setting passed in $data
4471       *
4472       * @param mixed gets converted to string for comparison
4473       * @return string empty string or error message
4474       */
4475      public function write_setting($data) {
4476          global $SESSION;
4477          return parent::write_setting($data);
4478      }
4479  }
4480  
4481  /**
4482   * Special select for settings that are altered in setup.php and can not be altered on the fly
4483   *
4484   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4485   */
4486  class admin_setting_special_selectsetup extends admin_setting_configselect {
4487      /**
4488       * Reads the setting directly from the database
4489       *
4490       * @return mixed
4491       */
4492      public function get_setting() {
4493      // read directly from db!
4494          return get_config(NULL, $this->name);
4495      }
4496  
4497      /**
4498       * Save the setting passed in $data
4499       *
4500       * @param string $data The setting to save
4501       * @return string empty or error message
4502       */
4503      public function write_setting($data) {
4504          global $CFG;
4505          // do not change active CFG setting!
4506          $current = $CFG->{$this->name};
4507          $result = parent::write_setting($data);
4508          $CFG->{$this->name} = $current;
4509          return $result;
4510      }
4511  }
4512  
4513  
4514  /**
4515   * Special select for frontpage - stores data in course table
4516   *
4517   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4518   */
4519  class admin_setting_sitesetselect extends admin_setting_configselect {
4520      /**
4521       * Returns the site name for the selected site
4522       *
4523       * @see get_site()
4524       * @return string The site name of the selected site
4525       */
4526      public function get_setting() {
4527          $site = course_get_format(get_site())->get_course();
4528          return $site->{$this->name};
4529      }
4530  
4531      /**
4532       * Updates the database and save the setting
4533       *
4534       * @param string data
4535       * @return string empty or error message
4536       */
4537      public function write_setting($data) {
4538          global $DB, $SITE, $COURSE;
4539          if (!in_array($data, array_keys($this->choices))) {
4540              return get_string('errorsetting', 'admin');
4541          }
4542          $record = new stdClass();
4543          $record->id           = SITEID;
4544          $temp                 = $this->name;
4545          $record->$temp        = $data;
4546          $record->timemodified = time();
4547  
4548          course_get_format($SITE)->update_course_format_options($record);
4549          $DB->update_record('course', $record);
4550  
4551          // Reset caches.
4552          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4553          if ($SITE->id == $COURSE->id) {
4554              $COURSE = $SITE;
4555          }
4556          core_courseformat\base::reset_course_cache($SITE->id);
4557  
4558          return '';
4559  
4560      }
4561  
4562      /**
4563       * admin_setting_sitesetselect is not meant to be overridden in config.php.
4564       *
4565       * @return bool
4566       */
4567      public function is_forceable(): bool {
4568          return false;
4569      }
4570  }
4571  
4572  
4573  /**
4574   * Select for blog's bloglevel setting: if set to 0, will set blog_menu
4575   * block to hidden.
4576   *
4577   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4578   */
4579  class admin_setting_bloglevel extends admin_setting_configselect {
4580      /**
4581       * Updates the database and save the setting
4582       *
4583       * @param string data
4584       * @return string empty or error message
4585       */
4586      public function write_setting($data) {
4587          global $DB, $CFG;
4588          if ($data == 0) {
4589              $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
4590              foreach ($blogblocks as $block) {
4591                  $DB->set_field('block', 'visible', 0, array('id' => $block->id));
4592              }
4593          } else {
4594              // reenable all blocks only when switching from disabled blogs
4595              if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
4596                  $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
4597                  foreach ($blogblocks as $block) {
4598                      $DB->set_field('block', 'visible', 1, array('id' => $block->id));
4599                  }
4600              }
4601          }
4602          return parent::write_setting($data);
4603      }
4604  }
4605  
4606  
4607  /**
4608   * Special select - lists on the frontpage - hacky
4609   *
4610   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4611   */
4612  class admin_setting_courselist_frontpage extends admin_setting {
4613      /** @var array Array of choices value=>label */
4614      public $choices;
4615  
4616      /**
4617       * Construct override, requires one param
4618       *
4619       * @param bool $loggedin Is the user logged in
4620       */
4621      public function __construct($loggedin) {
4622          global $CFG;
4623          require_once($CFG->dirroot.'/course/lib.php');
4624          $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
4625          $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
4626          $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
4627          $defaults    = array(FRONTPAGEALLCOURSELIST);
4628          parent::__construct($name, $visiblename, $description, $defaults);
4629      }
4630  
4631      /**
4632       * Loads the choices available
4633       *
4634       * @return bool always returns true
4635       */
4636      public function load_choices() {
4637          if (is_array($this->choices)) {
4638              return true;
4639          }
4640          $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
4641              FRONTPAGEALLCOURSELIST => get_string('frontpagecourselist'),
4642              FRONTPAGEENROLLEDCOURSELIST => get_string('frontpageenrolledcourselist'),
4643              FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
4644              FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
4645              FRONTPAGECOURSESEARCH  => get_string('frontpagecoursesearch'),
4646              'none'                 => get_string('none'));
4647          if ($this->name === 'frontpage') {
4648              unset($this->choices[FRONTPAGEENROLLEDCOURSELIST]);
4649          }
4650          return true;
4651      }
4652  
4653      /**
4654       * Returns the selected settings
4655       *
4656       * @param mixed array or setting or null
4657       */
4658      public function get_setting() {
4659          $result = $this->config_read($this->name);
4660          if (is_null($result)) {
4661              return NULL;
4662          }
4663          if ($result === '') {
4664              return array();
4665          }
4666          return explode(',', $result);
4667      }
4668  
4669      /**
4670       * Save the selected options
4671       *
4672       * @param array $data
4673       * @return mixed empty string (data is not an array) or bool true=success false=failure
4674       */
4675      public function write_setting($data) {
4676          if (!is_array($data)) {
4677              return '';
4678          }
4679          $this->load_choices();
4680          $save = array();
4681          foreach($data as $datum) {
4682              if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
4683                  continue;
4684              }
4685              $save[$datum] = $datum; // no duplicates
4686          }
4687          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
4688      }
4689  
4690      /**
4691       * Return XHTML select field and wrapping div
4692       *
4693       * @todo Add vartype handling to make sure $data is an array
4694       * @param array $data Array of elements to select by default
4695       * @return string XHTML select field and wrapping div
4696       */
4697      public function output_html($data, $query='') {
4698          global $OUTPUT;
4699  
4700          $this->load_choices();
4701          $currentsetting = array();
4702          foreach ($data as $key) {
4703              if ($key != 'none' and array_key_exists($key, $this->choices)) {
4704                  $currentsetting[] = $key; // already selected first
4705              }
4706          }
4707  
4708          $context = (object) [
4709              'id' => $this->get_id(),
4710              'name' => $this->get_full_name(),
4711          ];
4712  
4713          $options = $this->choices;
4714          $selects = [];
4715          for ($i = 0; $i < count($this->choices) - 1; $i++) {
4716              if (!array_key_exists($i, $currentsetting)) {
4717                  $currentsetting[$i] = 'none';
4718              }
4719              $selects[] = [
4720                  'key' => $i,
4721                  'options' => array_map(function($option) use ($options, $currentsetting, $i) {
4722                      return [
4723                          'name' => $options[$option],
4724                          'value' => $option,
4725                          'selected' => $currentsetting[$i] == $option
4726                      ];
4727                  }, array_keys($options))
4728              ];
4729          }
4730          $context->selects = $selects;
4731  
4732          $element = $OUTPUT->render_from_template('core_admin/setting_courselist_frontpage', $context);
4733  
4734          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
4735      }
4736  }
4737  
4738  
4739  /**
4740   * Special checkbox for frontpage - stores data in course table
4741   *
4742   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4743   */
4744  class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
4745      /**
4746       * Returns the current sites name
4747       *
4748       * @return string
4749       */
4750      public function get_setting() {
4751          $site = course_get_format(get_site())->get_course();
4752          return $site->{$this->name};
4753      }
4754  
4755      /**
4756       * Save the selected setting
4757       *
4758       * @param string $data The selected site
4759       * @return string empty string or error message
4760       */
4761      public function write_setting($data) {
4762          global $DB, $SITE, $COURSE;
4763          $record = new stdClass();
4764          $record->id            = $SITE->id;
4765          $record->{$this->name} = ($data == '1' ? 1 : 0);
4766          $record->timemodified  = time();
4767  
4768          course_get_format($SITE)->update_course_format_options($record);
4769          $DB->update_record('course', $record);
4770  
4771          // Reset caches.
4772          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4773          if ($SITE->id == $COURSE->id) {
4774              $COURSE = $SITE;
4775          }
4776          core_courseformat\base::reset_course_cache($SITE->id);
4777  
4778          return '';
4779      }
4780  
4781      /**
4782       * admin_setting_sitesetcheckbox is not meant to be overridden in config.php.
4783       *
4784       * @return bool
4785       */
4786      public function is_forceable(): bool {
4787          return false;
4788      }
4789  }
4790  
4791  /**
4792   * Special text for frontpage - stores data in course table.
4793   * Empty string means not set here. Manual setting is required.
4794   *
4795   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4796   */
4797  class admin_setting_sitesettext extends admin_setting_configtext {
4798  
4799      /**
4800       * Constructor.
4801       */
4802      public function __construct() {
4803          call_user_func_array(['parent', '__construct'], func_get_args());
4804          $this->set_force_ltr(false);
4805      }
4806  
4807      /**
4808       * Return the current setting
4809       *
4810       * @return mixed string or null
4811       */
4812      public function get_setting() {
4813          $site = course_get_format(get_site())->get_course();
4814          return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
4815      }
4816  
4817      /**
4818       * Validate the selected data
4819       *
4820       * @param string $data The selected value to validate
4821       * @return mixed true or message string
4822       */
4823      public function validate($data) {
4824          global $DB, $SITE;
4825          $cleaned = clean_param($data, PARAM_TEXT);
4826          if ($cleaned === '') {
4827              return get_string('required');
4828          }
4829          if ($this->name ==='shortname' &&
4830                  $DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data, $SITE->id))) {
4831              return get_string('shortnametaken', 'error', $data);
4832          }
4833          if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
4834              return true;
4835          } else {
4836              return get_string('validateerror', 'admin');
4837          }
4838      }
4839  
4840      /**
4841       * Save the selected setting
4842       *
4843       * @param string $data The selected value
4844       * @return string empty or error message
4845       */
4846      public function write_setting($data) {
4847          global $DB, $SITE, $COURSE;
4848          $data = trim($data);
4849          $validated = $this->validate($data);
4850          if ($validated !== true) {
4851              return $validated;
4852          }
4853  
4854          $record = new stdClass();
4855          $record->id            = $SITE->id;
4856          $record->{$this->name} = $data;
4857          $record->timemodified  = time();
4858  
4859          course_get_format($SITE)->update_course_format_options($record);
4860          $DB->update_record('course', $record);
4861  
4862          // Reset caches.
4863          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4864          if ($SITE->id == $COURSE->id) {
4865              $COURSE = $SITE;
4866          }
4867          core_courseformat\base::reset_course_cache($SITE->id);
4868  
4869          return '';
4870      }
4871  
4872      /**
4873       * admin_setting_sitesettext is not meant to be overridden in config.php.
4874       *
4875       * @return bool
4876       */
4877      public function is_forceable(): bool {
4878          return false;
4879      }
4880  }
4881  
4882  
4883  /**
4884   * This type of field should be used for mandatory config settings.
4885   *
4886   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4887   */
4888  class admin_setting_requiredtext extends admin_setting_configtext {
4889  
4890      /**
4891       * Validate data before storage.
4892       *
4893       * @param string $data The string to be validated.
4894       * @return bool|string true for success or error string if invalid.
4895       */
4896      public function validate($data) {
4897          $cleaned = clean_param($data, PARAM_TEXT);
4898          if ($cleaned === '') {
4899              return get_string('required');
4900          }
4901  
4902          return parent::validate($data);
4903      }
4904  }
4905  
4906  /**
4907   * Special text editor for site description.
4908   *
4909   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4910   */
4911  class admin_setting_special_frontpagedesc extends admin_setting_confightmleditor {
4912  
4913      /**
4914       * Calls parent::__construct with specific arguments
4915       */
4916      public function __construct() {
4917          parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), null,
4918              PARAM_RAW, 60, 15);
4919      }
4920  
4921      /**
4922       * Return the current setting
4923       * @return string The current setting
4924       */
4925      public function get_setting() {
4926          $site = course_get_format(get_site())->get_course();
4927          return $site->{$this->name};
4928      }
4929  
4930      /**
4931       * Save the new setting
4932       *
4933       * @param string $data The new value to save
4934       * @return string empty or error message
4935       */
4936      public function write_setting($data) {
4937          global $DB, $SITE, $COURSE;
4938          $record = new stdClass();
4939          $record->id            = $SITE->id;
4940          $record->{$this->name} = $data;
4941          $record->timemodified  = time();
4942  
4943          course_get_format($SITE)->update_course_format_options($record);
4944          $DB->update_record('course', $record);
4945  
4946          // Reset caches.
4947          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4948          if ($SITE->id == $COURSE->id) {
4949              $COURSE = $SITE;
4950          }
4951          core_courseformat\base::reset_course_cache($SITE->id);
4952  
4953          return '';
4954      }
4955  
4956      /**
4957       * admin_setting_special_frontpagedesc is not meant to be overridden in config.php.
4958       *
4959       * @return bool
4960       */
4961      public function is_forceable(): bool {
4962          return false;
4963      }
4964  }
4965  
4966  
4967  /**
4968   * Administration interface for emoticon_manager settings.
4969   *
4970   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4971   */
4972  class admin_setting_emoticons extends admin_setting {
4973  
4974      /**
4975       * Calls parent::__construct with specific args
4976       */
4977      public function __construct() {
4978          global $CFG;
4979  
4980          $manager = get_emoticon_manager();
4981          $defaults = $this->prepare_form_data($manager->default_emoticons());
4982          parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
4983      }
4984  
4985      /**
4986       * Return the current setting(s)
4987       *
4988       * @return array Current settings array
4989       */
4990      public function get_setting() {
4991          global $CFG;
4992  
4993          $manager = get_emoticon_manager();
4994  
4995          $config = $this->config_read($this->name);
4996          if (is_null($config)) {
4997              return null;
4998          }
4999  
5000          $config = $manager->decode_stored_config($config);
5001          if (is_null($config)) {
5002              return null;
5003          }
5004  
5005          return $this->prepare_form_data($config);
5006      }
5007  
5008      /**
5009       * Save selected settings
5010       *
5011       * @param array $data Array of settings to save
5012       * @return bool
5013       */
5014      public function write_setting($data) {
5015  
5016          $manager = get_emoticon_manager();
5017          $emoticons = $this->process_form_data($data);
5018  
5019          if ($emoticons === false) {
5020              return false;
5021          }
5022  
5023          if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
5024              return ''; // success
5025          } else {
5026              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
5027          }
5028      }
5029  
5030      /**
5031       * Return XHTML field(s) for options
5032       *
5033       * @param array $data Array of options to set in HTML
5034       * @return string XHTML string for the fields and wrapping div(s)
5035       */
5036      public function output_html($data, $query='') {
5037          global $OUTPUT;
5038  
5039          $context = (object) [
5040              'name' => $this->get_full_name(),
5041              'emoticons' => [],
5042              'forceltr' => true,
5043          ];
5044  
5045          $i = 0;
5046          foreach ($data as $field => $value) {
5047  
5048              // When $i == 0: text.
5049              // When $i == 1: imagename.
5050              // When $i == 2: imagecomponent.
5051              // When $i == 3: altidentifier.
5052              // When $i == 4: altcomponent.
5053              $fields[$i] = (object) [
5054                  'field' => $field,
5055                  'value' => $value,
5056                  'index' => $i
5057              ];
5058              $i++;
5059  
5060              if ($i > 4) {
5061                  $icon = null;
5062                  if (!empty($fields[1]->value)) {
5063                      if (get_string_manager()->string_exists($fields[3]->value, $fields[4]->value)) {
5064                          $alt = get_string($fields[3]->value, $fields[4]->value);
5065                      } else {
5066                          $alt = $fields[0]->value;
5067                      }
5068                      $icon = new pix_emoticon($fields[1]->value, $alt, $fields[2]->value);
5069                  }
5070                  $context->emoticons[] = [
5071                      'fields' => $fields,
5072                      'icon' => $icon ? $icon->export_for_template($OUTPUT) : null
5073                  ];
5074                  $fields = [];
5075                  $i = 0;
5076              }
5077          }
5078  
5079          $context->reseturl = new moodle_url('/admin/resetemoticons.php');
5080          $element = $OUTPUT->render_from_template('core_admin/setting_emoticons', $context);
5081          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
5082      }
5083  
5084      /**
5085       * Converts the array of emoticon objects provided by {@see emoticon_manager} into admin settings form data
5086       *
5087       * @see self::process_form_data()
5088       * @param array $emoticons array of emoticon objects as returned by {@see emoticon_manager}
5089       * @return array of form fields and their values
5090       */
5091      protected function prepare_form_data(array $emoticons) {
5092  
5093          $form = array();
5094          $i = 0;
5095          foreach ($emoticons as $emoticon) {
5096              $form['text'.$i]            = $emoticon->text;
5097              $form['imagename'.$i]       = $emoticon->imagename;
5098              $form['imagecomponent'.$i]  = $emoticon->imagecomponent;
5099              $form['altidentifier'.$i]   = $emoticon->altidentifier;
5100              $form['altcomponent'.$i]    = $emoticon->altcomponent;
5101              $i++;
5102          }
5103          // add one more blank field set for new object
5104          $form['text'.$i]            = '';
5105          $form['imagename'.$i]       = '';
5106          $form['imagecomponent'.$i]  = '';
5107          $form['altidentifier'.$i]   = '';
5108          $form['altcomponent'.$i]    = '';
5109  
5110          return $form;
5111      }
5112  
5113      /**
5114       * Converts the data from admin settings form into an array of emoticon objects
5115       *
5116       * @see self::prepare_form_data()
5117       * @param array $data array of admin form fields and values
5118       * @return false|array of emoticon objects
5119       */
5120      protected function process_form_data(array $form) {
5121  
5122          $count = count($form); // number of form field values
5123  
5124          if ($count % 5) {
5125              // we must get five fields per emoticon object
5126              return false;
5127          }
5128  
5129          $emoticons = array();
5130          for ($i = 0; $i < $count / 5; $i++) {
5131              $emoticon                   = new stdClass();
5132              $emoticon->text             = clean_param(trim($form['text'.$i]), PARAM_NOTAGS);
5133              $emoticon->imagename        = clean_param(trim($form['imagename'.$i]), PARAM_PATH);
5134              $emoticon->imagecomponent   = clean_param(trim($form['imagecomponent'.$i]), PARAM_COMPONENT);
5135              $emoticon->altidentifier    = clean_param(trim($form['altidentifier'.$i]), PARAM_STRINGID);
5136              $emoticon->altcomponent     = clean_param(trim($form['altcomponent'.$i]), PARAM_COMPONENT);
5137  
5138              if (strpos($emoticon->text, ':/') !== false or strpos($emoticon->text, '//') !== false) {
5139                  // prevent from breaking http://url.addresses by accident
5140                  $emoticon->text = '';
5141              }
5142  
5143              if (strlen($emoticon->text) < 2) {
5144                  // do not allow single character emoticons
5145                  $emoticon->text = '';
5146              }
5147  
5148              if (preg_match('/^[a-zA-Z]+[a-zA-Z0-9]*$/', $emoticon->text)) {
5149                  // emoticon text must contain some non-alphanumeric character to prevent
5150                  // breaking HTML tags
5151                  $emoticon->text = '';
5152              }
5153  
5154              if ($emoticon->text !== '' and $emoticon->imagename !== '' and $emoticon->imagecomponent !== '') {
5155                  $emoticons[] = $emoticon;
5156              }
5157          }
5158          return $emoticons;
5159      }
5160  
5161  }
5162  
5163  
5164  /**
5165   * Special setting for limiting of the list of available languages.
5166   *
5167   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5168   */
5169  class admin_setting_langlist extends admin_setting_configtext {
5170      /**
5171       * Calls parent::__construct with specific arguments
5172       */
5173      public function __construct() {
5174          parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
5175      }
5176  
5177      /**
5178       * Validate that each language identifier exists on the site
5179       *
5180       * @param string $data
5181       * @return bool|string True if validation successful, otherwise error string
5182       */
5183      public function validate($data) {
5184          $parentcheck = parent::validate($data);
5185          if ($parentcheck !== true) {
5186              return $parentcheck;
5187          }
5188  
5189          if ($data === '') {
5190              return true;
5191          }
5192  
5193          // Normalize language identifiers.
5194          $langcodes = array_map('trim', explode(',', $data));
5195          foreach ($langcodes as $langcode) {
5196              // If the langcode contains optional alias, split it out.
5197              [$langcode, ] = preg_split('/\s*\|\s*/', $langcode, 2);
5198  
5199              if (!get_string_manager()->translation_exists($langcode)) {
5200                  return get_string('invalidlanguagecode', 'error', $langcode);
5201              }
5202          }
5203  
5204          return true;
5205      }
5206  
5207      /**
5208       * Save the new setting
5209       *
5210       * @param string $data The new setting
5211       * @return bool
5212       */
5213      public function write_setting($data) {
5214          $return = parent::write_setting($data);
5215          get_string_manager()->reset_caches();
5216          return $return;
5217      }
5218  }
5219  
5220  
5221  /**
5222   * Allows to specify comma separated list of known country codes.
5223   *
5224   * This is a simple subclass of the plain input text field with added validation so that all the codes are actually
5225   * known codes.
5226   *
5227   * @package     core
5228   * @category    admin
5229   * @copyright   2020 David Mudrák <david@moodle.com>
5230   * @license     https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5231   */
5232  class admin_setting_countrycodes extends admin_setting_configtext {
5233  
5234      /**
5235       * Construct the instance of the setting.
5236       *
5237       * @param string $name Name of the admin setting such as 'allcountrycodes' or 'myplugin/countries'.
5238       * @param lang_string|string $visiblename Language string with the field label text.
5239       * @param lang_string|string $description Language string with the field description text.
5240       * @param string $defaultsetting Default value of the setting.
5241       * @param int $size Input text field size.
5242       */
5243      public function __construct($name, $visiblename, $description, $defaultsetting = '', $size = null) {
5244          parent::__construct($name, $visiblename, $description, $defaultsetting, '/^(?:\w+(?:,\w+)*)?$/', $size);
5245      }
5246  
5247      /**
5248       * Validate the setting value before storing it.
5249       *
5250       * The value is first validated through custom regex so that it is a word consisting of letters, numbers or underscore; or
5251       * a comma separated list of such words.
5252       *
5253       * @param string $data Value inserted into the setting field.
5254       * @return bool|string True if the value is OK, error string otherwise.
5255       */
5256      public function validate($data) {
5257  
5258          $parentcheck = parent::validate($data);
5259  
5260          if ($parentcheck !== true) {
5261              return $parentcheck;
5262          }
5263  
5264          if ($data === '') {
5265              return true;
5266          }
5267  
5268          $allcountries = get_string_manager()->get_list_of_countries(true);
5269  
5270          foreach (explode(',', $data) as $code) {
5271              if (!isset($allcountries[$code])) {
5272                  return get_string('invalidcountrycode', 'core_error', $code);
5273              }
5274          }
5275  
5276          return true;
5277      }
5278  }
5279  
5280  
5281  /**
5282   * Selection of one of the recognised countries using the list
5283   * returned by {@link get_list_of_countries()}.
5284   *
5285   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5286   */
5287  class admin_settings_country_select extends admin_setting_configselect {
5288      protected $includeall;
5289      public function __construct($name, $visiblename, $description, $defaultsetting, $includeall=false) {
5290          $this->includeall = $includeall;
5291          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
5292      }
5293  
5294      /**
5295       * Lazy-load the available choices for the select box
5296       */
5297      public function load_choices() {
5298          global $CFG;
5299          if (is_array($this->choices)) {
5300              return true;
5301          }
5302          $this->choices = array_merge(
5303                  array('0' => get_string('choosedots')),
5304                  get_string_manager()->get_list_of_countries($this->includeall));
5305          return true;
5306      }
5307  }
5308  
5309  
5310  /**
5311   * admin_setting_configselect for the default number of sections in a course,
5312   * simply so we can lazy-load the choices.
5313   *
5314   * @copyright 2011 The Open University
5315   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5316   */
5317  class admin_settings_num_course_sections extends admin_setting_configselect {
5318      public function __construct($name, $visiblename, $description, $defaultsetting) {
5319          parent::__construct($name, $visiblename, $description, $defaultsetting, array());
5320      }
5321  
5322      /** Lazy-load the available choices for the select box */
5323      public function load_choices() {
5324          $max = get_config('moodlecourse', 'maxsections');
5325          if (!isset($max) || !is_numeric($max)) {
5326              $max = 52;
5327          }
5328          for ($i = 0; $i <= $max; $i++) {
5329              $this->choices[$i] = "$i";
5330          }
5331          return true;
5332      }
5333  }
5334  
5335  
5336  /**
5337   * Course category selection
5338   *
5339   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5340   */
5341  class admin_settings_coursecat_select extends admin_setting_configselect_autocomplete {
5342      /**
5343       * Calls parent::__construct with specific arguments
5344       */
5345      public function __construct($name, $visiblename, $description, $defaultsetting = 1) {
5346          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices = null);
5347      }
5348  
5349      /**
5350       * Load the available choices for the select box
5351       *
5352       * @return bool
5353       */
5354      public function load_choices() {
5355          if (is_array($this->choices)) {
5356              return true;
5357          }
5358          $this->choices = core_course_category::make_categories_list('', 0, ' / ');
5359          return true;
5360      }
5361  }
5362  
5363  
5364  /**
5365   * Special control for selecting days to backup
5366   *
5367   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5368   */
5369  class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
5370      /**
5371       * Calls parent::__construct with specific arguments
5372       */
5373      public function __construct() {
5374          parent::__construct('backup_auto_weekdays', get_string('automatedbackupschedule','backup'), get_string('automatedbackupschedulehelp','backup'), array(), NULL);
5375          $this->plugin = 'backup';
5376      }
5377  
5378      /**
5379       * Load the available choices for the select box
5380       *
5381       * @return bool Always returns true
5382       */
5383      public function load_choices() {
5384          if (is_array($this->choices)) {
5385              return true;
5386          }
5387          $this->choices = array();
5388          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5389          foreach ($days as $day) {
5390              $this->choices[$day] = get_string($day, 'calendar');
5391          }
5392          return true;
5393      }
5394  }
5395  
5396  /**
5397   * Special setting for backup auto destination.
5398   *
5399   * @package    core
5400   * @subpackage admin
5401   * @copyright  2014 Frédéric Massart - FMCorz.net
5402   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5403   */
5404  class admin_setting_special_backup_auto_destination extends admin_setting_configdirectory {
5405  
5406      /**
5407       * Calls parent::__construct with specific arguments.
5408       */
5409      public function __construct() {
5410          parent::__construct('backup/backup_auto_destination', new lang_string('saveto'), new lang_string('backupsavetohelp'), '');
5411      }
5412  
5413      /**
5414       * Check if the directory must be set, depending on backup/backup_auto_storage.
5415       *
5416       * Note: backup/backup_auto_storage must be specified BEFORE this setting otherwise
5417       * there will be conflicts if this validation happens before the other one.
5418       *
5419       * @param string $data Form data.
5420       * @return string Empty when no errors.
5421       */
5422      public function write_setting($data) {
5423          $storage = (int) get_config('backup', 'backup_auto_storage');
5424          if ($storage !== 0) {
5425              if (empty($data) || !file_exists($data) || !is_dir($data) || !is_writable($data) ) {
5426                  // The directory must exist and be writable.
5427                  return get_string('backuperrorinvaliddestination');
5428              }
5429          }
5430          return parent::write_setting($data);
5431      }
5432  }
5433  
5434  
5435  /**
5436   * Special debug setting
5437   *
5438   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5439   */
5440  class admin_setting_special_debug extends admin_setting_configselect {
5441      /**
5442       * Calls parent::__construct with specific arguments
5443       */
5444      public function __construct() {
5445          parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
5446      }
5447  
5448      /**
5449       * Load the available choices for the select box
5450       *
5451       * @return bool
5452       */
5453      public function load_choices() {
5454          if (is_array($this->choices)) {
5455              return true;
5456          }
5457          $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
5458              DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
5459              DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
5460              DEBUG_ALL       => get_string('debugall', 'admin'),
5461              DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
5462          return true;
5463      }
5464  }
5465  
5466  
5467  /**
5468   * Special admin control
5469   *
5470   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5471   */
5472  class admin_setting_special_calendar_weekend extends admin_setting {
5473      /**
5474       * Calls parent::__construct with specific arguments
5475       */
5476      public function __construct() {
5477          $name = 'calendar_weekend';
5478          $visiblename = get_string('calendar_weekend', 'admin');
5479          $description = get_string('helpweekenddays', 'admin');
5480          $default = array ('0', '6'); // Saturdays and Sundays
5481          parent::__construct($name, $visiblename, $description, $default);
5482      }
5483  
5484      /**
5485       * Gets the current settings as an array
5486       *
5487       * @return mixed Null if none, else array of settings
5488       */
5489      public function get_setting() {
5490          $result = $this->config_read($this->name);
5491          if (is_null($result)) {
5492              return NULL;
5493          }
5494          if ($result === '') {
5495              return array();
5496          }
5497          $settings = array();
5498          for ($i=0; $i<7; $i++) {
5499              if ($result & (1 << $i)) {
5500                  $settings[] = $i;
5501              }
5502          }
5503          return $settings;
5504      }
5505  
5506      /**
5507       * Save the new settings
5508       *
5509       * @param array $data Array of new settings
5510       * @return bool
5511       */
5512      public function write_setting($data) {
5513          if (!is_array($data)) {
5514              return '';
5515          }
5516          unset($data['xxxxx']);
5517          $result = 0;
5518          foreach($data as $index) {
5519              $result |= 1 << $index;
5520          }
5521          return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
5522      }
5523  
5524      /**
5525       * Return XHTML to display the control
5526       *
5527       * @param array $data array of selected days
5528       * @param string $query
5529       * @return string XHTML for display (field + wrapping div(s)
5530       */
5531      public function output_html($data, $query='') {
5532          global $OUTPUT;
5533  
5534          // The order matters very much because of the implied numeric keys.
5535          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5536          $context = (object) [
5537              'name' => $this->get_full_name(),
5538              'id' => $this->get_id(),
5539              'days' => array_map(function($index) use ($days, $data) {
5540                  return [
5541                      'index' => $index,
5542                      'label' => get_string($days[$index], 'calendar'),
5543                      'checked' => in_array($index, $data)
5544                  ];
5545              }, array_keys($days))
5546          ];
5547  
5548          $element = $OUTPUT->render_from_template('core_admin/setting_special_calendar_weekend', $context);
5549  
5550          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
5551  
5552      }
5553  }
5554  
5555  
5556  /**
5557   * Admin setting that allows a user to pick a behaviour.
5558   *
5559   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5560   */
5561  class admin_setting_question_behaviour extends admin_setting_configselect {
5562      /**
5563       * @param string $name name of config variable
5564       * @param string $visiblename display name
5565       * @param string $description description
5566       * @param string $default default.
5567       */
5568      public function __construct($name, $visiblename, $description, $default) {
5569          parent::__construct($name, $visiblename, $description, $default, null);
5570      }
5571  
5572      /**
5573       * Load list of behaviours as choices
5574       * @return bool true => success, false => error.
5575       */
5576      public function load_choices() {
5577          global $CFG;
5578          require_once($CFG->dirroot . '/question/engine/lib.php');
5579          $this->choices = question_engine::get_behaviour_options('');
5580          return true;
5581      }
5582  }
5583  
5584  
5585  /**
5586   * Admin setting that allows a user to pick appropriate roles for something.
5587   *
5588   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5589   */
5590  class admin_setting_pickroles extends admin_setting_configmulticheckbox {
5591      /** @var array Array of capabilities which identify roles */
5592      private $types;
5593  
5594      /**
5595       * @param string $name Name of config variable
5596       * @param string $visiblename Display name
5597       * @param string $description Description
5598       * @param array $types Array of archetypes which identify
5599       *              roles that will be enabled by default.
5600       */
5601      public function __construct($name, $visiblename, $description, $types) {
5602          parent::__construct($name, $visiblename, $description, NULL, NULL);
5603          $this->types = $types;
5604      }
5605  
5606      /**
5607       * Load roles as choices
5608       *
5609       * @return bool true=>success, false=>error
5610       */
5611      public function load_choices() {
5612          global $CFG, $DB;
5613          if (during_initial_install()) {
5614              return false;
5615          }
5616          if (is_array($this->choices)) {
5617              return true;
5618          }
5619          if ($roles = get_all_roles()) {
5620              $this->choices = role_fix_names($roles, null, ROLENAME_ORIGINAL, true);
5621              return true;
5622          } else {
5623              return false;
5624          }
5625      }
5626  
5627      /**
5628       * Return the default setting for this control
5629       *
5630       * @return array Array of default settings
5631       */
5632      public function get_defaultsetting() {
5633          global $CFG;
5634  
5635          if (during_initial_install()) {
5636              return null;
5637          }
5638          $result = array();
5639          foreach($this->types as $archetype) {
5640              if ($caproles = get_archetype_roles($archetype)) {
5641                  foreach ($caproles as $caprole) {
5642                      $result[$caprole->id] = 1;
5643                  }
5644              }
5645          }
5646          return $result;
5647      }
5648  }
5649  
5650  
5651  /**
5652   * Admin setting that is a list of installed filter plugins.
5653   *
5654   * @copyright 2015 The Open University
5655   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5656   */
5657  class admin_setting_pickfilters extends admin_setting_configmulticheckbox {
5658  
5659      /**
5660       * Constructor
5661       *
5662       * @param string $name unique ascii name, either 'mysetting' for settings
5663       *      that in config, or 'myplugin/mysetting' for ones in config_plugins.
5664       * @param string $visiblename localised name
5665       * @param string $description localised long description
5666       * @param array $default the default. E.g. array('urltolink' => 1, 'emoticons' => 1)
5667       */
5668      public function __construct($name, $visiblename, $description, $default) {
5669          if (empty($default)) {
5670              $default = array();
5671          }
5672          $this->load_choices();
5673          foreach ($default as $plugin) {
5674              if (!isset($this->choices[$plugin])) {
5675                  unset($default[$plugin]);
5676              }
5677          }
5678          parent::__construct($name, $visiblename, $description, $default, null);
5679      }
5680  
5681      public function load_choices() {
5682          if (is_array($this->choices)) {
5683              return true;
5684          }
5685          $this->choices = array();
5686  
5687          foreach (core_component::get_plugin_list('filter') as $plugin => $unused) {
5688              $this->choices[$plugin] = filter_get_name($plugin);
5689          }
5690          return true;
5691      }
5692  }
5693  
5694  
5695  /**
5696   * Text field with an advanced checkbox, that controls a additional $name.'_adv' setting.
5697   *
5698   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5699   */
5700  class admin_setting_configtext_with_advanced extends admin_setting_configtext {
5701      /**
5702       * Constructor
5703       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5704       * @param string $visiblename localised
5705       * @param string $description long localised info
5706       * @param array $defaultsetting ('value'=>string, '__construct'=>bool)
5707       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
5708       * @param int $size default field size
5709       */
5710      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
5711          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $paramtype, $size);
5712          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5713      }
5714  }
5715  
5716  
5717  /**
5718   * Checkbox with an advanced checkbox that controls an additional $name.'_adv' config setting.
5719   *
5720   * @copyright 2009 Petr Skoda (http://skodak.org)
5721   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5722   */
5723  class admin_setting_configcheckbox_with_advanced extends admin_setting_configcheckbox {
5724  
5725      /**
5726       * Constructor
5727       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5728       * @param string $visiblename localised
5729       * @param string $description long localised info
5730       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
5731       * @param string $yes value used when checked
5732       * @param string $no value used when not checked
5733       */
5734      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5735          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5736          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5737      }
5738  
5739  }
5740  
5741  
5742  /**
5743   * Checkbox with an advanced checkbox that controls an additional $name.'_locked' config setting.
5744   *
5745   * This is nearly a copy/paste of admin_setting_configcheckbox_with_adv
5746   *
5747   * @copyright 2010 Sam Hemelryk
5748   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5749   */
5750  class admin_setting_configcheckbox_with_lock extends admin_setting_configcheckbox {
5751      /**
5752       * Constructor
5753       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5754       * @param string $visiblename localised
5755       * @param string $description long localised info
5756       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5757       * @param string $yes value used when checked
5758       * @param string $no value used when not checked
5759       */
5760      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5761          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5762          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5763      }
5764  
5765  }
5766  
5767  /**
5768   * Autocomplete as you type form element.
5769   *
5770   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5771   */
5772  class admin_setting_configselect_autocomplete extends admin_setting_configselect {
5773      /** @var boolean $tags Should we allow typing new entries to the field? */
5774      protected $tags = false;
5775      /** @var string $ajax Name of an AMD module to send/process ajax requests. */
5776      protected $ajax = '';
5777      /** @var string $placeholder Placeholder text for an empty list. */
5778      protected $placeholder = '';
5779      /** @var bool $casesensitive Whether the search has to be case-sensitive. */
5780      protected $casesensitive = false;
5781      /** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
5782      protected $showsuggestions = true;
5783      /** @var string $noselectionstring String that is shown when there are no selections. */
5784      protected $noselectionstring = '';
5785  
5786      /**
5787       * Returns XHTML select field and wrapping div(s)
5788       *
5789       * @see output_select_html()
5790       *
5791       * @param string $data the option to show as selected
5792       * @param string $query
5793       * @return string XHTML field and wrapping div
5794       */
5795      public function output_html($data, $query='') {
5796          global $PAGE;
5797  
5798          $html = parent::output_html($data, $query);
5799  
5800          if ($html === '') {
5801              return $html;
5802          }
5803  
5804          $this->placeholder = get_string('search');
5805  
5806          $params = array('#' . $this->get_id(), $this->tags, $this->ajax,
5807              $this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring);
5808  
5809          // Load autocomplete wrapper for select2 library.
5810          $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params);
5811  
5812          return $html;
5813      }
5814  }
5815  
5816  /**
5817   * Dropdown menu with an advanced checkbox, that controls a additional $name.'_adv' setting.
5818   *
5819   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5820   */
5821  class admin_setting_configselect_with_advanced extends admin_setting_configselect {
5822      /**
5823       * Calls parent::__construct with specific arguments
5824       */
5825      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5826          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5827          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5828      }
5829  
5830  }
5831  
5832  /**
5833   * Select with an advanced checkbox that controls an additional $name.'_locked' config setting.
5834   *
5835   * @copyright 2017 Marina Glancy
5836   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5837   */
5838  class admin_setting_configselect_with_lock extends admin_setting_configselect {
5839      /**
5840       * Constructor
5841       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
5842       *     or 'myplugin/mysetting' for ones in config_plugins.
5843       * @param string $visiblename localised
5844       * @param string $description long localised info
5845       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5846       * @param array $choices array of $value=>$label for each selection
5847       */
5848      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5849          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5850          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5851      }
5852  }
5853  
5854  
5855  /**
5856   * Graded roles in gradebook
5857   *
5858   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5859   */
5860  class admin_setting_special_gradebookroles extends admin_setting_pickroles {
5861      /**
5862       * Calls parent::__construct with specific arguments
5863       */
5864      public function __construct() {
5865          parent::__construct('gradebookroles', get_string('gradebookroles', 'admin'),
5866              get_string('configgradebookroles', 'admin'),
5867              array('student'));
5868      }
5869  }
5870  
5871  
5872  /**
5873   *
5874   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5875   */
5876  class admin_setting_regradingcheckbox extends admin_setting_configcheckbox {
5877      /**
5878       * Saves the new settings passed in $data
5879       *
5880       * @param string $data
5881       * @return mixed string or Array
5882       */
5883      public function write_setting($data) {
5884          global $CFG, $DB;
5885  
5886          $oldvalue  = $this->config_read($this->name);
5887          $return    = parent::write_setting($data);
5888          $newvalue  = $this->config_read($this->name);
5889  
5890          if ($oldvalue !== $newvalue) {
5891          // force full regrading
5892              $DB->set_field('grade_items', 'needsupdate', 1, array('needsupdate'=>0));
5893          }
5894  
5895          return $return;
5896      }
5897  }
5898  
5899  
5900  /**
5901   * Which roles to show on course description page
5902   *
5903   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5904   */
5905  class admin_setting_special_coursecontact extends admin_setting_pickroles {
5906      /**
5907       * Calls parent::__construct with specific arguments
5908       */
5909      public function __construct() {
5910          parent::__construct('coursecontact', get_string('coursecontact', 'admin'),
5911              get_string('coursecontact_desc', 'admin'),
5912              array('editingteacher'));
5913          $this->set_updatedcallback(function (){
5914              cache::make('core', 'coursecontacts')->purge();
5915          });
5916      }
5917  }
5918  
5919  
5920  /**
5921   *
5922   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5923   */
5924  class admin_setting_special_gradelimiting extends admin_setting_configcheckbox {
5925      /**
5926       * Calls parent::__construct with specific arguments
5927       */
5928      public function __construct() {
5929          parent::__construct('unlimitedgrades', get_string('unlimitedgrades', 'grades'),
5930              get_string('unlimitedgrades_help', 'grades'), '0', '1', '0');
5931      }
5932  
5933      /**
5934       * Old syntax of class constructor. Deprecated in PHP7.
5935       *
5936       * @deprecated since Moodle 3.1
5937       */
5938      public function admin_setting_special_gradelimiting() {
5939          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
5940          self::__construct();
5941      }
5942  
5943      /**
5944       * Force site regrading
5945       */
5946      function regrade_all() {
5947          global $CFG;
5948          require_once("$CFG->libdir/gradelib.php");
5949          grade_force_site_regrading();
5950      }
5951  
5952      /**
5953       * Saves the new settings
5954       *
5955       * @param mixed $data
5956       * @return string empty string or error message
5957       */
5958      function write_setting($data) {
5959          $previous = $this->get_setting();
5960  
5961          if ($previous === null) {
5962              if ($data) {
5963                  $this->regrade_all();
5964              }
5965          } else {
5966              if ($data != $previous) {
5967                  $this->regrade_all();
5968              }
5969          }
5970          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
5971      }
5972  
5973  }
5974  
5975  /**
5976   * Special setting for $CFG->grade_minmaxtouse.
5977   *
5978   * @package    core
5979   * @copyright  2015 Frédéric Massart - FMCorz.net
5980   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5981   */
5982  class admin_setting_special_grademinmaxtouse extends admin_setting_configselect {
5983  
5984      /**
5985       * Constructor.
5986       */
5987      public function __construct() {
5988          parent::__construct('grade_minmaxtouse', new lang_string('minmaxtouse', 'grades'),
5989              new lang_string('minmaxtouse_desc', 'grades'), GRADE_MIN_MAX_FROM_GRADE_ITEM,
5990              array(
5991                  GRADE_MIN_MAX_FROM_GRADE_ITEM => get_string('gradeitemminmax', 'grades'),
5992                  GRADE_MIN_MAX_FROM_GRADE_GRADE => get_string('gradegrademinmax', 'grades')
5993              )
5994          );
5995      }
5996  
5997      /**
5998       * Saves the new setting.
5999       *
6000       * @param mixed $data
6001       * @return string empty string or error message
6002       */
6003      function write_setting($data) {
6004          global $CFG;
6005  
6006          $previous = $this->get_setting();
6007          $result = parent::write_setting($data);
6008  
6009          // If saved and the value has changed.
6010          if (empty($result) && $previous != $data) {
6011              require_once($CFG->libdir . '/gradelib.php');
6012              grade_force_site_regrading();
6013          }
6014  
6015          return $result;
6016      }
6017  
6018  }
6019  
6020  
6021  /**
6022   * Primary grade export plugin - has state tracking.
6023   *
6024   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6025   */
6026  class admin_setting_special_gradeexport extends admin_setting_configmulticheckbox {
6027      /**
6028       * Calls parent::__construct with specific arguments
6029       */
6030      public function __construct() {
6031          parent::__construct('gradeexport', get_string('gradeexport', 'admin'),
6032              get_string('configgradeexport', 'admin'), array(), NULL);
6033      }
6034  
6035      /**
6036       * Load the available choices for the multicheckbox
6037       *
6038       * @return bool always returns true
6039       */
6040      public function load_choices() {
6041          if (is_array($this->choices)) {
6042              return true;
6043          }
6044          $this->choices = array();
6045  
6046          if ($plugins = core_component::get_plugin_list('gradeexport')) {
6047              foreach($plugins as $plugin => $unused) {
6048                  $this->choices[$plugin] = get_string('pluginname', 'gradeexport_'.$plugin);
6049              }
6050          }
6051          return true;
6052      }
6053  }
6054  
6055  
6056  /**
6057   * A setting for setting the default grade point value. Must be an integer between 1 and $CFG->gradepointmax.
6058   *
6059   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6060   */
6061  class admin_setting_special_gradepointdefault extends admin_setting_configtext {
6062      /**
6063       * Config gradepointmax constructor
6064       *
6065       * @param string $name Overidden by "gradepointmax"
6066       * @param string $visiblename Overridden by "gradepointmax" language string.
6067       * @param string $description Overridden by "gradepointmax_help" language string.
6068       * @param string $defaultsetting Not used, overridden by 100.
6069       * @param mixed $paramtype Overridden by PARAM_INT.
6070       * @param int $size Overridden by 5.
6071       */
6072      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
6073          $name = 'gradepointdefault';
6074          $visiblename = get_string('gradepointdefault', 'grades');
6075          $description = get_string('gradepointdefault_help', 'grades');
6076          $defaultsetting = 100;
6077          $paramtype = PARAM_INT;
6078          $size = 5;
6079          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
6080      }
6081  
6082      /**
6083       * Validate data before storage
6084       * @param string $data The submitted data
6085       * @return bool|string true if ok, string if error found
6086       */
6087      public function validate($data) {
6088          global $CFG;
6089          if (((string)(int)$data === (string)$data && $data > 0 && $data <= $CFG->gradepointmax)) {
6090              return true;
6091          } else {
6092              return get_string('gradepointdefault_validateerror', 'grades');
6093          }
6094      }
6095  }
6096  
6097  
6098  /**
6099   * A setting for setting the maximum grade value. Must be an integer between 1 and 10000.
6100   *
6101   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6102   */
6103  class admin_setting_special_gradepointmax extends admin_setting_configtext {
6104  
6105      /**
6106       * Config gradepointmax constructor
6107       *
6108       * @param string $name Overidden by "gradepointmax"
6109       * @param string $visiblename Overridden by "gradepointmax" language string.
6110       * @param string $description Overridden by "gradepointmax_help" language string.
6111       * @param string $defaultsetting Not used, overridden by 100.
6112       * @param mixed $paramtype Overridden by PARAM_INT.
6113       * @param int $size Overridden by 5.
6114       */
6115      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
6116          $name = 'gradepointmax';
6117          $visiblename = get_string('gradepointmax', 'grades');
6118          $description = get_string('gradepointmax_help', 'grades');
6119          $defaultsetting = 100;
6120          $paramtype = PARAM_INT;
6121          $size = 5;
6122          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
6123      }
6124  
6125      /**
6126       * Save the selected setting
6127       *
6128       * @param string $data The selected site
6129       * @return string empty string or error message
6130       */
6131      public function write_setting($data) {
6132          if ($data === '') {
6133              $data = (int)$this->defaultsetting;
6134          } else {
6135              $data = $data;
6136          }
6137          return parent::write_setting($data);
6138      }
6139  
6140      /**
6141       * Validate data before storage
6142       * @param string $data The submitted data
6143       * @return bool|string true if ok, string if error found
6144       */
6145      public function validate($data) {
6146          if (((string)(int)$data === (string)$data && $data > 0 && $data <= 10000)) {
6147              return true;
6148          } else {
6149              return get_string('gradepointmax_validateerror', 'grades');
6150          }
6151      }
6152  
6153      /**
6154       * Return an XHTML string for the setting
6155       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6156       * @param string $query search query to be highlighted
6157       * @return string XHTML to display control
6158       */
6159      public function output_html($data, $query = '') {
6160          global $OUTPUT;
6161  
6162          $default = $this->get_defaultsetting();
6163          $context = (object) [
6164              'size' => $this->size,
6165              'id' => $this->get_id(),
6166              'name' => $this->get_full_name(),
6167              'value' => $data,
6168              'attributes' => [
6169                  'maxlength' => 5
6170              ],
6171              'forceltr' => $this->get_force_ltr()
6172          ];
6173          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
6174  
6175          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
6176      }
6177  }
6178  
6179  
6180  /**
6181   * Grade category settings
6182   *
6183   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6184   */
6185  class admin_setting_gradecat_combo extends admin_setting {
6186      /** @var array Array of choices */
6187      public $choices;
6188  
6189      /**
6190       * Sets choices and calls parent::__construct with passed arguments
6191       * @param string $name
6192       * @param string $visiblename
6193       * @param string $description
6194       * @param mixed $defaultsetting string or array depending on implementation
6195       * @param array $choices An array of choices for the control
6196       */
6197      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
6198          $this->choices = $choices;
6199          parent::__construct($name, $visiblename, $description, $defaultsetting);
6200      }
6201  
6202      /**
6203       * Return the current setting(s) array
6204       *
6205       * @return array Array of value=>xx, forced=>xx, adv=>xx
6206       */
6207      public function get_setting() {
6208          global $CFG;
6209  
6210          $value = $this->config_read($this->name);
6211          $flag  = $this->config_read($this->name.'_flag');
6212  
6213          if (is_null($value) or is_null($flag)) {
6214              return NULL;
6215          }
6216  
6217          $flag   = (int)$flag;
6218          $forced = (boolean)(1 & $flag); // first bit
6219          $adv    = (boolean)(2 & $flag); // second bit
6220  
6221          return array('value' => $value, 'forced' => $forced, 'adv' => $adv);
6222      }
6223  
6224      /**
6225       * Save the new settings passed in $data
6226       *
6227       * @todo Add vartype handling to ensure $data is array
6228       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6229       * @return string empty or error message
6230       */
6231      public function write_setting($data) {
6232          global $CFG;
6233  
6234          $value  = $data['value'];
6235          $forced = empty($data['forced']) ? 0 : 1;
6236          $adv    = empty($data['adv'])    ? 0 : 2;
6237          $flag   = ($forced | $adv); //bitwise or
6238  
6239          if (!in_array($value, array_keys($this->choices))) {
6240              return 'Error setting ';
6241          }
6242  
6243          $oldvalue  = $this->config_read($this->name);
6244          $oldflag   = (int)$this->config_read($this->name.'_flag');
6245          $oldforced = (1 & $oldflag); // first bit
6246  
6247          $result1 = $this->config_write($this->name, $value);
6248          $result2 = $this->config_write($this->name.'_flag', $flag);
6249  
6250          // force regrade if needed
6251          if ($oldforced != $forced or ($forced and $value != $oldvalue)) {
6252              require_once($CFG->libdir.'/gradelib.php');
6253              grade_category::updated_forced_settings();
6254          }
6255  
6256          if ($result1 and $result2) {
6257              return '';
6258          } else {
6259              return get_string('errorsetting', 'admin');
6260          }
6261      }
6262  
6263      /**
6264       * Return XHTML to display the field and wrapping div
6265       *
6266       * @todo Add vartype handling to ensure $data is array
6267       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6268       * @param string $query
6269       * @return string XHTML to display control
6270       */
6271      public function output_html($data, $query='') {
6272          global $OUTPUT;
6273  
6274          $value  = $data['value'];
6275  
6276          $default = $this->get_defaultsetting();
6277          if (!is_null($default)) {
6278              $defaultinfo = array();
6279              if (isset($this->choices[$default['value']])) {
6280                  $defaultinfo[] = $this->choices[$default['value']];
6281              }
6282              if (!empty($default['forced'])) {
6283                  $defaultinfo[] = get_string('force');
6284              }
6285              if (!empty($default['adv'])) {
6286                  $defaultinfo[] = get_string('advanced');
6287              }
6288              $defaultinfo = implode(', ', $defaultinfo);
6289  
6290          } else {
6291              $defaultinfo = NULL;
6292          }
6293  
6294          $options = $this->choices;
6295          $context = (object) [
6296              'id' => $this->get_id(),
6297              'name' => $this->get_full_name(),
6298              'forced' => !empty($data['forced']),
6299              'advanced' => !empty($data['adv']),
6300              'options' => array_map(function($option) use ($options, $value) {
6301                  return [
6302                      'value' => $option,
6303                      'name' => $options[$option],
6304                      'selected' => $option == $value
6305                  ];
6306              }, array_keys($options)),
6307          ];
6308  
6309          $element = $OUTPUT->render_from_template('core_admin/setting_gradecat_combo', $context);
6310  
6311          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
6312      }
6313  }
6314  
6315  
6316  /**
6317   * Selection of grade report in user profiles
6318   *
6319   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6320   */
6321  class admin_setting_grade_profilereport extends admin_setting_configselect {
6322      /**
6323       * Calls parent::__construct with specific arguments
6324       */
6325      public function __construct() {
6326          parent::__construct('grade_profilereport', get_string('profilereport', 'grades'), get_string('profilereport_help', 'grades'), 'user', null);
6327      }
6328  
6329      /**
6330       * Loads an array of choices for the configselect control
6331       *
6332       * @return bool always return true
6333       */
6334      public function load_choices() {
6335          if (is_array($this->choices)) {
6336              return true;
6337          }
6338          $this->choices = array();
6339  
6340          global $CFG;
6341          require_once($CFG->libdir.'/gradelib.php');
6342  
6343          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6344              if (file_exists($plugindir.'/lib.php')) {
6345                  require_once($plugindir.'/lib.php');
6346                  $functionname = 'grade_report_'.$plugin.'_profilereport';
6347                  if (function_exists($functionname)) {
6348                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_'.$plugin);
6349                  }
6350              }
6351          }
6352          return true;
6353      }
6354  }
6355  
6356  /**
6357   * Provides a selection of grade reports to be used for "grades".
6358   *
6359   * @copyright 2015 Adrian Greeve <adrian@moodle.com>
6360   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6361   */
6362  class admin_setting_my_grades_report extends admin_setting_configselect {
6363  
6364      /**
6365       * Calls parent::__construct with specific arguments.
6366       */
6367      public function __construct() {
6368          parent::__construct('grade_mygrades_report', new lang_string('mygrades', 'grades'),
6369                  new lang_string('mygrades_desc', 'grades'), 'overview', null);
6370      }
6371  
6372      /**
6373       * Loads an array of choices for the configselect control.
6374       *
6375       * @return bool always returns true.
6376       */
6377      public function load_choices() {
6378          global $CFG; // Remove this line and behold the horror of behat test failures!
6379          $this->choices = array();
6380          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6381              if (file_exists($plugindir . '/lib.php')) {
6382                  require_once($plugindir . '/lib.php');
6383                  // Check to see if the class exists. Check the correct plugin convention first.
6384                  if (class_exists('gradereport_' . $plugin)) {
6385                      $classname = 'gradereport_' . $plugin;
6386                  } else if (class_exists('grade_report_' . $plugin)) {
6387                      // We are using the old plugin naming convention.
6388                      $classname = 'grade_report_' . $plugin;
6389                  } else {
6390                      continue;
6391                  }
6392                  if ($classname::supports_mygrades()) {
6393                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_' . $plugin);
6394                  }
6395              }
6396          }
6397          // Add an option to specify an external url.
6398          $this->choices['external'] = get_string('externalurl', 'grades');
6399          return true;
6400      }
6401  }
6402  
6403  /**
6404   * Special class for register auth selection
6405   *
6406   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6407   */
6408  class admin_setting_special_registerauth extends admin_setting_configselect {
6409      /**
6410       * Calls parent::__construct with specific arguments
6411       */
6412      public function __construct() {
6413          parent::__construct('registerauth', get_string('selfregistration', 'auth'), get_string('selfregistration_help', 'auth'), '', null);
6414      }
6415  
6416      /**
6417       * Returns the default option
6418       *
6419       * @return string empty or default option
6420       */
6421      public function get_defaultsetting() {
6422          $this->load_choices();
6423          $defaultsetting = parent::get_defaultsetting();
6424          if (array_key_exists($defaultsetting, $this->choices)) {
6425              return $defaultsetting;
6426          } else {
6427              return '';
6428          }
6429      }
6430  
6431      /**
6432       * Loads the possible choices for the array
6433       *
6434       * @return bool always returns true
6435       */
6436      public function load_choices() {
6437          global $CFG;
6438  
6439          if (is_array($this->choices)) {
6440              return true;
6441          }
6442          $this->choices = array();
6443          $this->choices[''] = get_string('disable');
6444  
6445          $authsenabled = get_enabled_auth_plugins();
6446  
6447          foreach ($authsenabled as $auth) {
6448              $authplugin = get_auth_plugin($auth);
6449              if (!$authplugin->can_signup()) {
6450                  continue;
6451              }
6452              // Get the auth title (from core or own auth lang files)
6453              $authtitle = $authplugin->get_title();
6454              $this->choices[$auth] = $authtitle;
6455          }
6456          return true;
6457      }
6458  }
6459  
6460  
6461  /**
6462   * General plugins manager
6463   */
6464  class admin_page_pluginsoverview extends admin_externalpage {
6465  
6466      /**
6467       * Sets basic information about the external page
6468       */
6469      public function __construct() {
6470          global $CFG;
6471          parent::__construct('pluginsoverview', get_string('pluginsoverview', 'core_admin'),
6472              "$CFG->wwwroot/$CFG->admin/plugins.php");
6473      }
6474  }
6475  
6476  /**
6477   * Module manage page
6478   *
6479   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6480   */
6481  class admin_page_managemods extends admin_externalpage {
6482      /**
6483       * Calls parent::__construct with specific arguments
6484       */
6485      public function __construct() {
6486          global $CFG;
6487          parent::__construct('managemodules', get_string('modsettings', 'admin'), "$CFG->wwwroot/$CFG->admin/modules.php");
6488      }
6489  
6490      /**
6491       * Try to find the specified module
6492       *
6493       * @param string $query The module to search for
6494       * @return array
6495       */
6496      public function search($query) {
6497          global $CFG, $DB;
6498          if ($result = parent::search($query)) {
6499              return $result;
6500          }
6501  
6502          $found = false;
6503          if ($modules = $DB->get_records('modules')) {
6504              foreach ($modules as $module) {
6505                  if (!file_exists("$CFG->dirroot/mod/$module->name/lib.php")) {
6506                      continue;
6507                  }
6508                  if (strpos($module->name, $query) !== false) {
6509                      $found = true;
6510                      break;
6511                  }
6512                  $strmodulename = get_string('modulename', $module->name);
6513                  if (strpos(core_text::strtolower($strmodulename), $query) !== false) {
6514                      $found = true;
6515                      break;
6516                  }
6517              }
6518          }
6519          if ($found) {
6520              $result = new stdClass();
6521              $result->page     = $this;
6522              $result->settings = array();
6523              return array($this->name => $result);
6524          } else {
6525              return array();
6526          }
6527      }
6528  }
6529  
6530  
6531  /**
6532   * Special class for enrol plugins management.
6533   *
6534   * @copyright 2010 Petr Skoda {@link http://skodak.org}
6535   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6536   */
6537  class admin_setting_manageenrols extends admin_setting {
6538      /**
6539       * Calls parent::__construct with specific arguments
6540       */
6541      public function __construct() {
6542          $this->nosave = true;
6543          parent::__construct('enrolsui', get_string('manageenrols', 'enrol'), '', '');
6544      }
6545  
6546      /**
6547       * Always returns true, does nothing
6548       *
6549       * @return true
6550       */
6551      public function get_setting() {
6552          return true;
6553      }
6554  
6555      /**
6556       * Always returns true, does nothing
6557       *
6558       * @return true
6559       */
6560      public function get_defaultsetting() {
6561          return true;
6562      }
6563  
6564      /**
6565       * Always returns '', does not write anything
6566       *
6567       * @return string Always returns ''
6568       */
6569      public function write_setting($data) {
6570      // do not write any setting
6571          return '';
6572      }
6573  
6574      /**
6575       * Checks if $query is one of the available enrol plugins
6576       *
6577       * @param string $query The string to search for
6578       * @return bool Returns true if found, false if not
6579       */
6580      public function is_related($query) {
6581          if (parent::is_related($query)) {
6582              return true;
6583          }
6584  
6585          $query = core_text::strtolower($query);
6586          $enrols = enrol_get_plugins(false);
6587          foreach ($enrols as $name=>$enrol) {
6588              $localised = get_string('pluginname', 'enrol_'.$name);
6589              if (strpos(core_text::strtolower($name), $query) !== false) {
6590                  return true;
6591              }
6592              if (strpos(core_text::strtolower($localised), $query) !== false) {
6593                  return true;
6594              }
6595          }
6596          return false;
6597      }
6598  
6599      /**
6600       * Builds the XHTML to display the control
6601       *
6602       * @param string $data Unused
6603       * @param string $query
6604       * @return string
6605       */
6606      public function output_html($data, $query='') {
6607          global $CFG, $OUTPUT, $DB, $PAGE;
6608  
6609          // Display strings.
6610          $strup        = get_string('up');
6611          $strdown      = get_string('down');
6612          $strsettings  = get_string('settings');
6613          $strenable    = get_string('enable');
6614          $strdisable   = get_string('disable');
6615          $struninstall = get_string('uninstallplugin', 'core_admin');
6616          $strusage     = get_string('enrolusage', 'enrol');
6617          $strversion   = get_string('version');
6618          $strtest      = get_string('testsettings', 'core_enrol');
6619  
6620          $pluginmanager = core_plugin_manager::instance();
6621  
6622          $enrols_available = enrol_get_plugins(false);
6623          $active_enrols    = enrol_get_plugins(true);
6624  
6625          $allenrols = array();
6626          foreach ($active_enrols as $key=>$enrol) {
6627              $allenrols[$key] = true;
6628          }
6629          foreach ($enrols_available as $key=>$enrol) {
6630              $allenrols[$key] = true;
6631          }
6632          // Now find all borked plugins and at least allow then to uninstall.
6633          $condidates = $DB->get_fieldset_sql("SELECT DISTINCT enrol FROM {enrol}");
6634          foreach ($condidates as $candidate) {
6635              if (empty($allenrols[$candidate])) {
6636                  $allenrols[$candidate] = true;
6637              }
6638          }
6639  
6640          $return = $OUTPUT->heading(get_string('actenrolshhdr', 'enrol'), 3, 'main', true);
6641          $return .= $OUTPUT->box_start('generalbox enrolsui');
6642  
6643          $table = new html_table();
6644          $table->head  = array(get_string('name'), $strusage, $strversion, $strenable, $strup.'/'.$strdown, $strsettings, $strtest, $struninstall);
6645          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
6646          $table->id = 'courseenrolmentplugins';
6647          $table->attributes['class'] = 'admintable generaltable';
6648          $table->data  = array();
6649  
6650          // Iterate through enrol plugins and add to the display table.
6651          $updowncount = 1;
6652          $enrolcount = count($active_enrols);
6653          $url = new moodle_url('/admin/enrol.php', array('sesskey'=>sesskey()));
6654          $printed = array();
6655          foreach($allenrols as $enrol => $unused) {
6656              $plugininfo = $pluginmanager->get_plugin_info('enrol_'.$enrol);
6657              $version = get_config('enrol_'.$enrol, 'version');
6658              if ($version === false) {
6659                  $version = '';
6660              }
6661  
6662              if (get_string_manager()->string_exists('pluginname', 'enrol_'.$enrol)) {
6663                  $name = get_string('pluginname', 'enrol_'.$enrol);
6664              } else {
6665                  $name = $enrol;
6666              }
6667              // Usage.
6668              $ci = $DB->count_records('enrol', array('enrol'=>$enrol));
6669              $cp = $DB->count_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($enrol));
6670              $usage = "$ci / $cp";
6671  
6672              // Hide/show links.
6673              $class = '';
6674              if (isset($active_enrols[$enrol])) {
6675                  $aurl = new moodle_url($url, array('action'=>'disable', 'enrol'=>$enrol));
6676                  $hideshow = "<a href=\"$aurl\">";
6677                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
6678                  $enabled = true;
6679                  $displayname = $name;
6680              } else if (isset($enrols_available[$enrol])) {
6681                  $aurl = new moodle_url($url, array('action'=>'enable', 'enrol'=>$enrol));
6682                  $hideshow = "<a href=\"$aurl\">";
6683                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
6684                  $enabled = false;
6685                  $displayname = $name;
6686                  $class = 'dimmed_text';
6687              } else {
6688                  $hideshow = '';
6689                  $enabled = false;
6690                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
6691              }
6692              if ($PAGE->theme->resolve_image_location('icon', 'enrol_' . $name, false)) {
6693                  $icon = $OUTPUT->pix_icon('icon', '', 'enrol_' . $name, array('class' => 'icon pluginicon'));
6694              } else {
6695                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
6696              }
6697  
6698              // Up/down link (only if enrol is enabled).
6699              $updown = '';
6700              if ($enabled) {
6701                  if ($updowncount > 1) {
6702                      $aurl = new moodle_url($url, array('action'=>'up', 'enrol'=>$enrol));
6703                      $updown .= "<a href=\"$aurl\">";
6704                      $updown .= $OUTPUT->pix_icon('t/up', $strup) . '</a>&nbsp;';
6705                  } else {
6706                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6707                  }
6708                  if ($updowncount < $enrolcount) {
6709                      $aurl = new moodle_url($url, array('action'=>'down', 'enrol'=>$enrol));
6710                      $updown .= "<a href=\"$aurl\">";
6711                      $updown .= $OUTPUT->pix_icon('t/down', $strdown) . '</a>&nbsp;';
6712                  } else {
6713                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6714                  }
6715                  ++$updowncount;
6716              }
6717  
6718              // Add settings link.
6719              if (!$version) {
6720                  $settings = '';
6721              } else if ($surl = $plugininfo->get_settings_url()) {
6722                  $settings = html_writer::link($surl, $strsettings);
6723              } else {
6724                  $settings = '';
6725              }
6726  
6727              // Add uninstall info.
6728              $uninstall = '';
6729              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol, 'manage')) {
6730                  $uninstall = html_writer::link($uninstallurl, $struninstall);
6731              }
6732  
6733              $test = '';
6734              if (!empty($enrols_available[$enrol]) and method_exists($enrols_available[$enrol], 'test_settings')) {
6735                  $testsettingsurl = new moodle_url('/enrol/test_settings.php', array('enrol'=>$enrol, 'sesskey'=>sesskey()));
6736                  $test = html_writer::link($testsettingsurl, $strtest);
6737              }
6738  
6739              // Add a row to the table.
6740              $row = new html_table_row(array($icon.$displayname, $usage, $version, $hideshow, $updown, $settings, $test, $uninstall));
6741              if ($class) {
6742                  $row->attributes['class'] = $class;
6743              }
6744              $table->data[] = $row;
6745  
6746              $printed[$enrol] = true;
6747          }
6748  
6749          $return .= html_writer::table($table);
6750          $return .= get_string('configenrolplugins', 'enrol').'<br />'.get_string('tablenosave', 'admin');
6751          $return .= $OUTPUT->box_end();
6752          return highlight($query, $return);
6753      }
6754  }
6755  
6756  
6757  /**
6758   * Blocks manage page
6759   *
6760   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6761   */
6762  class admin_page_manageblocks extends admin_externalpage {
6763      /**
6764       * Calls parent::__construct with specific arguments
6765       */
6766      public function __construct() {
6767          global $CFG;
6768          parent::__construct('manageblocks', get_string('blocksettings', 'admin'), "$CFG->wwwroot/$CFG->admin/blocks.php");
6769      }
6770  
6771      /**
6772       * Search for a specific block
6773       *
6774       * @param string $query The string to search for
6775       * @return array
6776       */
6777      public function search($query) {
6778          global $CFG, $DB;
6779          if ($result = parent::search($query)) {
6780              return $result;
6781          }
6782  
6783          $found = false;
6784          if ($blocks = $DB->get_records('block')) {
6785              foreach ($blocks as $block) {
6786                  if (!file_exists("$CFG->dirroot/blocks/$block->name/")) {
6787                      continue;
6788                  }
6789                  if (strpos($block->name, $query) !== false) {
6790                      $found = true;
6791                      break;
6792                  }
6793                  $strblockname = get_string('pluginname', 'block_'.$block->name);
6794                  if (strpos(core_text::strtolower($strblockname), $query) !== false) {
6795                      $found = true;
6796                      break;
6797                  }
6798              }
6799          }
6800          if ($found) {
6801              $result = new stdClass();
6802              $result->page     = $this;
6803              $result->settings = array();
6804              return array($this->name => $result);
6805          } else {
6806              return array();
6807          }
6808      }
6809  }
6810  
6811  /**
6812   * Message outputs configuration
6813   *
6814   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6815   */
6816  class admin_page_managemessageoutputs extends admin_externalpage {
6817      /**
6818       * Calls parent::__construct with specific arguments
6819       */
6820      public function __construct() {
6821          global $CFG;
6822          parent::__construct('managemessageoutputs',
6823              get_string('defaultmessageoutputs', 'message'),
6824              new moodle_url('/admin/message.php')
6825          );
6826      }
6827  
6828      /**
6829       * Search for a specific message processor
6830       *
6831       * @param string $query The string to search for
6832       * @return array
6833       */
6834      public function search($query) {
6835          global $CFG, $DB;
6836          if ($result = parent::search($query)) {
6837              return $result;
6838          }
6839  
6840          $found = false;
6841          if ($processors = get_message_processors()) {
6842              foreach ($processors as $processor) {
6843                  if (!$processor->available) {
6844                      continue;
6845                  }
6846                  if (strpos($processor->name, $query) !== false) {
6847                      $found = true;
6848                      break;
6849                  }
6850                  $strprocessorname = get_string('pluginname', 'message_'.$processor->name);
6851                  if (strpos(core_text::strtolower($strprocessorname), $query) !== false) {
6852                      $found = true;
6853                      break;
6854                  }
6855              }
6856          }
6857          if ($found) {
6858              $result = new stdClass();
6859              $result->page     = $this;
6860              $result->settings = array();
6861              return array($this->name => $result);
6862          } else {
6863              return array();
6864          }
6865      }
6866  }
6867  
6868  /**
6869   * Manage question behaviours page
6870   *
6871   * @copyright  2011 The Open University
6872   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6873   */
6874  class admin_page_manageqbehaviours extends admin_externalpage {
6875      /**
6876       * Constructor
6877       */
6878      public function __construct() {
6879          global $CFG;
6880          parent::__construct('manageqbehaviours', get_string('manageqbehaviours', 'admin'),
6881                  new moodle_url('/admin/qbehaviours.php'));
6882      }
6883  
6884      /**
6885       * Search question behaviours for the specified string
6886       *
6887       * @param string $query The string to search for in question behaviours
6888       * @return array
6889       */
6890      public function search($query) {
6891          global $CFG;
6892          if ($result = parent::search($query)) {
6893              return $result;
6894          }
6895  
6896          $found = false;
6897          require_once($CFG->dirroot . '/question/engine/lib.php');
6898          foreach (core_component::get_plugin_list('qbehaviour') as $behaviour => $notused) {
6899              if (strpos(core_text::strtolower(question_engine::get_behaviour_name($behaviour)),
6900                      $query) !== false) {
6901                  $found = true;
6902                  break;
6903              }
6904          }
6905          if ($found) {
6906              $result = new stdClass();
6907              $result->page     = $this;
6908              $result->settings = array();
6909              return array($this->name => $result);
6910          } else {
6911              return array();
6912          }
6913      }
6914  }
6915  
6916  
6917  /**
6918   * Question type manage page
6919   *
6920   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6921   */
6922  class admin_page_manageqtypes extends admin_externalpage {
6923      /**
6924       * Calls parent::__construct with specific arguments
6925       */
6926      public function __construct() {
6927          global $CFG;
6928          parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'),
6929                  new moodle_url('/admin/qtypes.php'));
6930      }
6931  
6932      /**
6933       * Search question types for the specified string
6934       *
6935       * @param string $query The string to search for in question types
6936       * @return array
6937       */
6938      public function search($query) {
6939          global $CFG;
6940          if ($result = parent::search($query)) {
6941              return $result;
6942          }
6943  
6944          $found = false;
6945          require_once($CFG->dirroot . '/question/engine/bank.php');
6946          foreach (question_bank::get_all_qtypes() as $qtype) {
6947              if (strpos(core_text::strtolower($qtype->local_name()), $query) !== false) {
6948                  $found = true;
6949                  break;
6950              }
6951          }
6952          if ($found) {
6953              $result = new stdClass();
6954              $result->page     = $this;
6955              $result->settings = array();
6956              return array($this->name => $result);
6957          } else {
6958              return array();
6959          }
6960      }
6961  }
6962  
6963  
6964  class admin_page_manageportfolios extends admin_externalpage {
6965      /**
6966       * Calls parent::__construct with specific arguments
6967       */
6968      public function __construct() {
6969          global $CFG;
6970          parent::__construct('manageportfolios', get_string('manageportfolios', 'portfolio'),
6971                  "$CFG->wwwroot/$CFG->admin/portfolio.php");
6972      }
6973  
6974      /**
6975       * Searches page for the specified string.
6976       * @param string $query The string to search for
6977       * @return bool True if it is found on this page
6978       */
6979      public function search($query) {
6980          global $CFG;
6981          if ($result = parent::search($query)) {
6982              return $result;
6983          }
6984  
6985          $found = false;
6986          $portfolios = core_component::get_plugin_list('portfolio');
6987          foreach ($portfolios as $p => $dir) {
6988              if (strpos($p, $query) !== false) {
6989                  $found = true;
6990                  break;
6991              }
6992          }
6993          if (!$found) {
6994              foreach (portfolio_instances(false, false) as $instance) {
6995                  $title = $instance->get('name');
6996                  if (strpos(core_text::strtolower($title), $query) !== false) {
6997                      $found = true;
6998                      break;
6999                  }
7000              }
7001          }
7002  
7003          if ($found) {
7004              $result = new stdClass();
7005              $result->page     = $this;
7006              $result->settings = array();
7007              return array($this->name => $result);
7008          } else {
7009              return array();
7010          }
7011      }
7012  }
7013  
7014  
7015  class admin_page_managerepositories extends admin_externalpage {
7016      /**
7017       * Calls parent::__construct with specific arguments
7018       */
7019      public function __construct() {
7020          global $CFG;
7021          parent::__construct('managerepositories', get_string('manage',
7022                  'repository'), "$CFG->wwwroot/$CFG->admin/repository.php");
7023      }
7024  
7025      /**
7026       * Searches page for the specified string.
7027       * @param string $query The string to search for
7028       * @return bool True if it is found on this page
7029       */
7030      public function search($query) {
7031          global $CFG;
7032          if ($result = parent::search($query)) {
7033              return $result;
7034          }
7035  
7036          $found = false;
7037          $repositories= core_component::get_plugin_list('repository');
7038          foreach ($repositories as $p => $dir) {
7039              if (strpos($p, $query) !== false) {
7040                  $found = true;
7041                  break;
7042              }
7043          }
7044          if (!$found) {
7045              foreach (repository::get_types() as $instance) {
7046                  $title = $instance->get_typename();
7047                  if (strpos(core_text::strtolower($title), $query) !== false) {
7048                      $found = true;
7049                      break;
7050                  }
7051              }
7052          }
7053  
7054          if ($found) {
7055              $result = new stdClass();
7056              $result->page     = $this;
7057              $result->settings = array();
7058              return array($this->name => $result);
7059          } else {
7060              return array();
7061          }
7062      }
7063  }
7064  
7065  
7066  /**
7067   * Special class for authentication administration.
7068   *
7069   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7070   */
7071  class admin_setting_manageauths extends admin_setting {
7072      /**
7073       * Calls parent::__construct with specific arguments
7074       */
7075      public function __construct() {
7076          $this->nosave = true;
7077          parent::__construct('authsui', get_string('authsettings', 'admin'), '', '');
7078      }
7079  
7080      /**
7081       * Always returns true
7082       *
7083       * @return true
7084       */
7085      public function get_setting() {
7086          return true;
7087      }
7088  
7089      /**
7090       * Always returns true
7091       *
7092       * @return true
7093       */
7094      public function get_defaultsetting() {
7095          return true;
7096      }
7097  
7098      /**
7099       * Always returns '' and doesn't write anything
7100       *
7101       * @return string Always returns ''
7102       */
7103      public function write_setting($data) {
7104      // do not write any setting
7105          return '';
7106      }
7107  
7108      /**
7109       * Search to find if Query is related to auth plugin
7110       *
7111       * @param string $query The string to search for
7112       * @return bool true for related false for not
7113       */
7114      public function is_related($query) {
7115          if (parent::is_related($query)) {
7116              return true;
7117          }
7118  
7119          $authsavailable = core_component::get_plugin_list('auth');
7120          foreach ($authsavailable as $auth => $dir) {
7121              if (strpos($auth, $query) !== false) {
7122                  return true;
7123              }
7124              $authplugin = get_auth_plugin($auth);
7125              $authtitle = $authplugin->get_title();
7126              if (strpos(core_text::strtolower($authtitle), $query) !== false) {
7127                  return true;
7128              }
7129          }
7130          return false;
7131      }
7132  
7133      /**
7134       * Return XHTML to display control
7135       *
7136       * @param mixed $data Unused
7137       * @param string $query
7138       * @return string highlight
7139       */
7140      public function output_html($data, $query='') {
7141          global $CFG, $OUTPUT, $DB;
7142  
7143          // display strings
7144          $txt = get_strings(array('authenticationplugins', 'users', 'administration',
7145              'settings', 'edit', 'name', 'enable', 'disable',
7146              'up', 'down', 'none', 'users'));
7147          $txt->updown = "$txt->up/$txt->down";
7148          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7149          $txt->testsettings = get_string('testsettings', 'core_auth');
7150  
7151          $authsavailable = core_component::get_plugin_list('auth');
7152          get_enabled_auth_plugins(true); // fix the list of enabled auths
7153          if (empty($CFG->auth)) {
7154              $authsenabled = array();
7155          } else {
7156              $authsenabled = explode(',', $CFG->auth);
7157          }
7158  
7159          // construct the display array, with enabled auth plugins at the top, in order
7160          $displayauths = array();
7161          $registrationauths = array();
7162          $registrationauths[''] = $txt->disable;
7163          $authplugins = array();
7164          foreach ($authsenabled as $auth) {
7165              $authplugin = get_auth_plugin($auth);
7166              $authplugins[$auth] = $authplugin;
7167              /// Get the auth title (from core or own auth lang files)
7168              $authtitle = $authplugin->get_title();
7169              /// Apply titles
7170              $displayauths[$auth] = $authtitle;
7171              if ($authplugin->can_signup()) {
7172                  $registrationauths[$auth] = $authtitle;
7173              }
7174          }
7175  
7176          foreach ($authsavailable as $auth => $dir) {
7177              if (array_key_exists($auth, $displayauths)) {
7178                  continue; //already in the list
7179              }
7180              $authplugin = get_auth_plugin($auth);
7181              $authplugins[$auth] = $authplugin;
7182              /// Get the auth title (from core or own auth lang files)
7183              $authtitle = $authplugin->get_title();
7184              /// Apply titles
7185              $displayauths[$auth] = $authtitle;
7186              if ($authplugin->can_signup()) {
7187                  $registrationauths[$auth] = $authtitle;
7188              }
7189          }
7190  
7191          $return = $OUTPUT->heading(get_string('actauthhdr', 'auth'), 3, 'main');
7192          $return .= $OUTPUT->box_start('generalbox authsui');
7193  
7194          $table = new html_table();
7195          $table->head  = array($txt->name, $txt->users, $txt->enable, $txt->updown, $txt->settings, $txt->testsettings, $txt->uninstall);
7196          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7197          $table->data  = array();
7198          $table->attributes['class'] = 'admintable generaltable';
7199          $table->id = 'manageauthtable';
7200  
7201          //add always enabled plugins first
7202          $displayname = $displayauths['manual'];
7203          $settings = "<a href=\"settings.php?section=authsettingmanual\">{$txt->settings}</a>";
7204          $usercount = $DB->count_records('user', array('auth'=>'manual', 'deleted'=>0));
7205          $table->data[] = array($displayname, $usercount, '', '', $settings, '', '');
7206          $displayname = $displayauths['nologin'];
7207          $usercount = $DB->count_records('user', array('auth'=>'nologin', 'deleted'=>0));
7208          $table->data[] = array($displayname, $usercount, '', '', '', '', '');
7209  
7210  
7211          // iterate through auth plugins and add to the display table
7212          $updowncount = 1;
7213          $authcount = count($authsenabled);
7214          $url = "auth.php?sesskey=" . sesskey();
7215          foreach ($displayauths as $auth => $name) {
7216              if ($auth == 'manual' or $auth == 'nologin') {
7217                  continue;
7218              }
7219              $class = '';
7220              // hide/show link
7221              if (in_array($auth, $authsenabled)) {
7222                  $hideshow = "<a href=\"$url&amp;action=disable&amp;auth=$auth\">";
7223                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7224                  $enabled = true;
7225                  $displayname = $name;
7226              }
7227              else {
7228                  $hideshow = "<a href=\"$url&amp;action=enable&amp;auth=$auth\">";
7229                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7230                  $enabled = false;
7231                  $displayname = $name;
7232                  $class = 'dimmed_text';
7233              }
7234  
7235              $usercount = $DB->count_records('user', array('auth'=>$auth, 'deleted'=>0));
7236  
7237              // up/down link (only if auth is enabled)
7238              $updown = '';
7239              if ($enabled) {
7240                  if ($updowncount > 1) {
7241                      $updown .= "<a href=\"$url&amp;action=up&amp;auth=$auth\">";
7242                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7243                  }
7244                  else {
7245                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7246                  }
7247                  if ($updowncount < $authcount) {
7248                      $updown .= "<a href=\"$url&amp;action=down&amp;auth=$auth\">";
7249                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7250                  }
7251                  else {
7252                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7253                  }
7254                  ++ $updowncount;
7255              }
7256  
7257              // settings link
7258              if (file_exists($CFG->dirroot.'/auth/'.$auth.'/settings.php')) {
7259                  $settings = "<a href=\"settings.php?section=authsetting$auth\">{$txt->settings}</a>";
7260              } else if (file_exists($CFG->dirroot.'/auth/'.$auth.'/config.html')) {
7261                  $settings = "<a href=\"auth_config.php?auth=$auth\">{$txt->settings}</a>";
7262              } else {
7263                  $settings = '';
7264              }
7265  
7266              // Uninstall link.
7267              $uninstall = '';
7268              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('auth_'.$auth, 'manage')) {
7269                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7270              }
7271  
7272              $test = '';
7273              if (!empty($authplugins[$auth]) and method_exists($authplugins[$auth], 'test_settings')) {
7274                  $testurl = new moodle_url('/auth/test_settings.php', array('auth'=>$auth, 'sesskey'=>sesskey()));
7275                  $test = html_writer::link($testurl, $txt->testsettings);
7276              }
7277  
7278              // Add a row to the table.
7279              $row = new html_table_row(array($displayname, $usercount, $hideshow, $updown, $settings, $test, $uninstall));
7280              if ($class) {
7281                  $row->attributes['class'] = $class;
7282              }
7283              $table->data[] = $row;
7284          }
7285          $return .= html_writer::table($table);
7286          $return .= get_string('configauthenticationplugins', 'admin').'<br />'.get_string('tablenosave', 'filters');
7287          $return .= $OUTPUT->box_end();
7288          return highlight($query, $return);
7289      }
7290  }
7291  
7292  
7293  /**
7294   * Special class for authentication administration.
7295   *
7296   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7297   */
7298  class admin_setting_manageeditors extends admin_setting {
7299      /**
7300       * Calls parent::__construct with specific arguments
7301       */
7302      public function __construct() {
7303          $this->nosave = true;
7304          parent::__construct('editorsui', get_string('editorsettings', 'editor'), '', '');
7305      }
7306  
7307      /**
7308       * Always returns true, does nothing
7309       *
7310       * @return true
7311       */
7312      public function get_setting() {
7313          return true;
7314      }
7315  
7316      /**
7317       * Always returns true, does nothing
7318       *
7319       * @return true
7320       */
7321      public function get_defaultsetting() {
7322          return true;
7323      }
7324  
7325      /**
7326       * Always returns '', does not write anything
7327       *
7328       * @return string Always returns ''
7329       */
7330      public function write_setting($data) {
7331      // do not write any setting
7332          return '';
7333      }
7334  
7335      /**
7336       * Checks if $query is one of the available editors
7337       *
7338       * @param string $query The string to search for
7339       * @return bool Returns true if found, false if not
7340       */
7341      public function is_related($query) {
7342          if (parent::is_related($query)) {
7343              return true;
7344          }
7345  
7346          $editors_available = editors_get_available();
7347          foreach ($editors_available as $editor=>$editorstr) {
7348              if (strpos($editor, $query) !== false) {
7349                  return true;
7350              }
7351              if (strpos(core_text::strtolower($editorstr), $query) !== false) {
7352                  return true;
7353              }
7354          }
7355          return false;
7356      }
7357  
7358      /**
7359       * Builds the XHTML to display the control
7360       *
7361       * @param string $data Unused
7362       * @param string $query
7363       * @return string
7364       */
7365      public function output_html($data, $query='') {
7366          global $CFG, $OUTPUT;
7367  
7368          // display strings
7369          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7370              'up', 'down', 'none'));
7371          $struninstall = get_string('uninstallplugin', 'core_admin');
7372  
7373          $txt->updown = "$txt->up/$txt->down";
7374  
7375          $editors_available = editors_get_available();
7376          $active_editors = explode(',', $CFG->texteditors);
7377  
7378          $active_editors = array_reverse($active_editors);
7379          foreach ($active_editors as $key=>$editor) {
7380              if (empty($editors_available[$editor])) {
7381                  unset($active_editors[$key]);
7382              } else {
7383                  $name = $editors_available[$editor];
7384                  unset($editors_available[$editor]);
7385                  $editors_available[$editor] = $name;
7386              }
7387          }
7388          if (empty($active_editors)) {
7389          //$active_editors = array('textarea');
7390          }
7391          $editors_available = array_reverse($editors_available, true);
7392          $return = $OUTPUT->heading(get_string('acteditorshhdr', 'editor'), 3, 'main', true);
7393          $return .= $OUTPUT->box_start('generalbox editorsui');
7394  
7395          $table = new html_table();
7396          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7397          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7398          $table->id = 'editormanagement';
7399          $table->attributes['class'] = 'admintable generaltable';
7400          $table->data  = array();
7401  
7402          // iterate through auth plugins and add to the display table
7403          $updowncount = 1;
7404          $editorcount = count($active_editors);
7405          $url = "editors.php?sesskey=" . sesskey();
7406          foreach ($editors_available as $editor => $name) {
7407          // hide/show link
7408              $class = '';
7409              if (in_array($editor, $active_editors)) {
7410                  $hideshow = "<a href=\"$url&amp;action=disable&amp;editor=$editor\">";
7411                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7412                  $enabled = true;
7413                  $displayname = $name;
7414              }
7415              else {
7416                  $hideshow = "<a href=\"$url&amp;action=enable&amp;editor=$editor\">";
7417                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7418                  $enabled = false;
7419                  $displayname = $name;
7420                  $class = 'dimmed_text';
7421              }
7422  
7423              // up/down link (only if auth is enabled)
7424              $updown = '';
7425              if ($enabled) {
7426                  if ($updowncount > 1) {
7427                      $updown .= "<a href=\"$url&amp;action=up&amp;editor=$editor\">";
7428                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7429                  }
7430                  else {
7431                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7432                  }
7433                  if ($updowncount < $editorcount) {
7434                      $updown .= "<a href=\"$url&amp;action=down&amp;editor=$editor\">";
7435                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7436                  }
7437                  else {
7438                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7439                  }
7440                  ++ $updowncount;
7441              }
7442  
7443              // settings link
7444              if (file_exists($CFG->dirroot.'/lib/editor/'.$editor.'/settings.php')) {
7445                  $eurl = new moodle_url('/admin/settings.php', array('section'=>'editorsettings'.$editor));
7446                  $settings = "<a href='$eurl'>{$txt->settings}</a>";
7447              } else {
7448                  $settings = '';
7449              }
7450  
7451              $uninstall = '';
7452              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('editor_'.$editor, 'manage')) {
7453                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7454              }
7455  
7456              // Add a row to the table.
7457              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7458              if ($class) {
7459                  $row->attributes['class'] = $class;
7460              }
7461              $table->data[] = $row;
7462          }
7463          $return .= html_writer::table($table);
7464          $return .= get_string('configeditorplugins', 'editor').'<br />'.get_string('tablenosave', 'admin');
7465          $return .= $OUTPUT->box_end();
7466          return highlight($query, $return);
7467      }
7468  }
7469  
7470  /**
7471   * Special class for antiviruses administration.
7472   *
7473   * @copyright  2015 Ruslan Kabalin, Lancaster University.
7474   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7475   */
7476  class admin_setting_manageantiviruses extends admin_setting {
7477      /**
7478       * Calls parent::__construct with specific arguments
7479       */
7480      public function __construct() {
7481          $this->nosave = true;
7482          parent::__construct('antivirusesui', get_string('antivirussettings', 'antivirus'), '', '');
7483      }
7484  
7485      /**
7486       * Always returns true, does nothing
7487       *
7488       * @return true
7489       */
7490      public function get_setting() {
7491          return true;
7492      }
7493  
7494      /**
7495       * Always returns true, does nothing
7496       *
7497       * @return true
7498       */
7499      public function get_defaultsetting() {
7500          return true;
7501      }
7502  
7503      /**
7504       * Always returns '', does not write anything
7505       *
7506       * @param string $data Unused
7507       * @return string Always returns ''
7508       */
7509      public function write_setting($data) {
7510          // Do not write any setting.
7511          return '';
7512      }
7513  
7514      /**
7515       * Checks if $query is one of the available editors
7516       *
7517       * @param string $query The string to search for
7518       * @return bool Returns true if found, false if not
7519       */
7520      public function is_related($query) {
7521          if (parent::is_related($query)) {
7522              return true;
7523          }
7524  
7525          $antivirusesavailable = \core\antivirus\manager::get_available();
7526          foreach ($antivirusesavailable as $antivirus => $antivirusstr) {
7527              if (strpos($antivirus, $query) !== false) {
7528                  return true;
7529              }
7530              if (strpos(core_text::strtolower($antivirusstr), $query) !== false) {
7531                  return true;
7532              }
7533          }
7534          return false;
7535      }
7536  
7537      /**
7538       * Builds the XHTML to display the control
7539       *
7540       * @param string $data Unused
7541       * @param string $query
7542       * @return string
7543       */
7544      public function output_html($data, $query='') {
7545          global $CFG, $OUTPUT;
7546  
7547          // Display strings.
7548          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7549              'up', 'down', 'none'));
7550          $struninstall = get_string('uninstallplugin', 'core_admin');
7551  
7552          $txt->updown = "$txt->up/$txt->down";
7553  
7554          $antivirusesavailable = \core\antivirus\manager::get_available();
7555          $activeantiviruses = explode(',', $CFG->antiviruses);
7556  
7557          $activeantiviruses = array_reverse($activeantiviruses);
7558          foreach ($activeantiviruses as $key => $antivirus) {
7559              if (empty($antivirusesavailable[$antivirus])) {
7560                  unset($activeantiviruses[$key]);
7561              } else {
7562                  $name = $antivirusesavailable[$antivirus];
7563                  unset($antivirusesavailable[$antivirus]);
7564                  $antivirusesavailable[$antivirus] = $name;
7565              }
7566          }
7567          $antivirusesavailable = array_reverse($antivirusesavailable, true);
7568          $return = $OUTPUT->heading(get_string('actantivirushdr', 'antivirus'), 3, 'main', true);
7569          $return .= $OUTPUT->box_start('generalbox antivirusesui');
7570  
7571          $table = new html_table();
7572          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7573          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7574          $table->id = 'antivirusmanagement';
7575          $table->attributes['class'] = 'admintable generaltable';
7576          $table->data  = array();
7577  
7578          // Iterate through auth plugins and add to the display table.
7579          $updowncount = 1;
7580          $antiviruscount = count($activeantiviruses);
7581          $baseurl = new moodle_url('/admin/antiviruses.php', array('sesskey' => sesskey()));
7582          foreach ($antivirusesavailable as $antivirus => $name) {
7583              // Hide/show link.
7584              $class = '';
7585              if (in_array($antivirus, $activeantiviruses)) {
7586                  $hideshowurl = $baseurl;
7587                  $hideshowurl->params(array('action' => 'disable', 'antivirus' => $antivirus));
7588                  $hideshowimg = $OUTPUT->pix_icon('t/hide', get_string('disable'));
7589                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7590                  $enabled = true;
7591                  $displayname = $name;
7592              } else {
7593                  $hideshowurl = $baseurl;
7594                  $hideshowurl->params(array('action' => 'enable', 'antivirus' => $antivirus));
7595                  $hideshowimg = $OUTPUT->pix_icon('t/show', get_string('enable'));
7596                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7597                  $enabled = false;
7598                  $displayname = $name;
7599                  $class = 'dimmed_text';
7600              }
7601  
7602              // Up/down link.
7603              $updown = '';
7604              if ($enabled) {
7605                  if ($updowncount > 1) {
7606                      $updownurl = $baseurl;
7607                      $updownurl->params(array('action' => 'up', 'antivirus' => $antivirus));
7608                      $updownimg = $OUTPUT->pix_icon('t/up', get_string('moveup'));
7609                      $updown = html_writer::link($updownurl, $updownimg);
7610                  } else {
7611                      $updownimg = $OUTPUT->spacer();
7612                  }
7613                  if ($updowncount < $antiviruscount) {
7614                      $updownurl = $baseurl;
7615                      $updownurl->params(array('action' => 'down', 'antivirus' => $antivirus));
7616                      $updownimg = $OUTPUT->pix_icon('t/down', get_string('movedown'));
7617                      $updown = html_writer::link($updownurl, $updownimg);
7618                  } else {
7619                      $updownimg = $OUTPUT->spacer();
7620                  }
7621                  ++ $updowncount;
7622              }
7623  
7624              // Settings link.
7625              if (file_exists($CFG->dirroot.'/lib/antivirus/'.$antivirus.'/settings.php')) {
7626                  $eurl = new moodle_url('/admin/settings.php', array('section' => 'antivirussettings'.$antivirus));
7627                  $settings = html_writer::link($eurl, $txt->settings);
7628              } else {
7629                  $settings = '';
7630              }
7631  
7632              $uninstall = '';
7633              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('antivirus_'.$antivirus, 'manage')) {
7634                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7635              }
7636  
7637              // Add a row to the table.
7638              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7639              if ($class) {
7640                  $row->attributes['class'] = $class;
7641              }
7642              $table->data[] = $row;
7643          }
7644          $return .= html_writer::table($table);
7645          $return .= get_string('configantivirusplugins', 'antivirus') . html_writer::empty_tag('br') . get_string('tablenosave', 'admin');
7646          $return .= $OUTPUT->box_end();
7647          return highlight($query, $return);
7648      }
7649  }
7650  
7651  /**
7652   * Course formats manager. Allows to enable/disable formats and jump to settings
7653   */
7654  class admin_setting_manageformats extends admin_setting {
7655  
7656      /**
7657       * Calls parent::__construct with specific arguments
7658       */
7659      public function __construct() {
7660          $this->nosave = true;
7661          parent::__construct('formatsui', new lang_string('manageformats', 'core_admin'), '', '');
7662      }
7663  
7664      /**
7665       * Always returns true
7666       *
7667       * @return true
7668       */
7669      public function get_setting() {
7670          return true;
7671      }
7672  
7673      /**
7674       * Always returns true
7675       *
7676       * @return true
7677       */
7678      public function get_defaultsetting() {
7679          return true;
7680      }
7681  
7682      /**
7683       * Always returns '' and doesn't write anything
7684       *
7685       * @param mixed $data string or array, must not be NULL
7686       * @return string Always returns ''
7687       */
7688      public function write_setting($data) {
7689          // do not write any setting
7690          return '';
7691      }
7692  
7693      /**
7694       * Search to find if Query is related to format plugin
7695       *
7696       * @param string $query The string to search for
7697       * @return bool true for related false for not
7698       */
7699      public function is_related($query) {
7700          if (parent::is_related($query)) {
7701              return true;
7702          }
7703          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7704          foreach ($formats as $format) {
7705              if (strpos($format->component, $query) !== false ||
7706                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7707                  return true;
7708              }
7709          }
7710          return false;
7711      }
7712  
7713      /**
7714       * Return XHTML to display control
7715       *
7716       * @param mixed $data Unused
7717       * @param string $query
7718       * @return string highlight
7719       */
7720      public function output_html($data, $query='') {
7721          global $CFG, $OUTPUT;
7722          $return = '';
7723          $return = $OUTPUT->heading(new lang_string('courseformats'), 3, 'main');
7724          $return .= $OUTPUT->box_start('generalbox formatsui');
7725  
7726          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7727  
7728          // display strings
7729          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
7730          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7731          $txt->updown = "$txt->up/$txt->down";
7732  
7733          $table = new html_table();
7734          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
7735          $table->align = array('left', 'center', 'center', 'center', 'center');
7736          $table->attributes['class'] = 'manageformattable generaltable admintable';
7737          $table->data  = array();
7738  
7739          $cnt = 0;
7740          $defaultformat = get_config('moodlecourse', 'format');
7741          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7742          foreach ($formats as $format) {
7743              $url = new moodle_url('/admin/courseformats.php',
7744                      array('sesskey' => sesskey(), 'format' => $format->name));
7745              $isdefault = '';
7746              $class = '';
7747              if ($format->is_enabled()) {
7748                  $strformatname = $format->displayname;
7749                  if ($defaultformat === $format->name) {
7750                      $hideshow = $txt->default;
7751                  } else {
7752                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7753                              $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7754                  }
7755              } else {
7756                  $strformatname = $format->displayname;
7757                  $class = 'dimmed_text';
7758                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7759                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7760              }
7761              $updown = '';
7762              if ($cnt) {
7763                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
7764                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
7765              } else {
7766                  $updown .= $spacer;
7767              }
7768              if ($cnt < count($formats) - 1) {
7769                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
7770                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
7771              } else {
7772                  $updown .= $spacer;
7773              }
7774              $cnt++;
7775              $settings = '';
7776              if ($format->get_settings_url()) {
7777                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
7778              }
7779              $uninstall = '';
7780              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('format_'.$format->name, 'manage')) {
7781                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7782              }
7783              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
7784              if ($class) {
7785                  $row->attributes['class'] = $class;
7786              }
7787              $table->data[] = $row;
7788          }
7789          $return .= html_writer::table($table);
7790          $link = html_writer::link(new moodle_url('/admin/settings.php', array('section' => 'coursesettings')), new lang_string('coursesettings'));
7791          $return .= html_writer::tag('p', get_string('manageformatsgotosettings', 'admin', $link));
7792          $return .= $OUTPUT->box_end();
7793          return highlight($query, $return);
7794      }
7795  }
7796  
7797  /**
7798   * Custom fields manager. Allows to enable/disable custom fields and jump to settings.
7799   *
7800   * @package    core
7801   * @copyright  2018 Toni Barbera
7802   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7803   */
7804  class admin_setting_managecustomfields extends admin_setting {
7805  
7806      /**
7807       * Calls parent::__construct with specific arguments
7808       */
7809      public function __construct() {
7810          $this->nosave = true;
7811          parent::__construct('customfieldsui', new lang_string('managecustomfields', 'core_admin'), '', '');
7812      }
7813  
7814      /**
7815       * Always returns true
7816       *
7817       * @return true
7818       */
7819      public function get_setting() {
7820          return true;
7821      }
7822  
7823      /**
7824       * Always returns true
7825       *
7826       * @return true
7827       */
7828      public function get_defaultsetting() {
7829          return true;
7830      }
7831  
7832      /**
7833       * Always returns '' and doesn't write anything
7834       *
7835       * @param mixed $data string or array, must not be NULL
7836       * @return string Always returns ''
7837       */
7838      public function write_setting($data) {
7839          // Do not write any setting.
7840          return '';
7841      }
7842  
7843      /**
7844       * Search to find if Query is related to format plugin
7845       *
7846       * @param string $query The string to search for
7847       * @return bool true for related false for not
7848       */
7849      public function is_related($query) {
7850          if (parent::is_related($query)) {
7851              return true;
7852          }
7853          $formats = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7854          foreach ($formats as $format) {
7855              if (strpos($format->component, $query) !== false ||
7856                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7857                  return true;
7858              }
7859          }
7860          return false;
7861      }
7862  
7863      /**
7864       * Return XHTML to display control
7865       *
7866       * @param mixed $data Unused
7867       * @param string $query
7868       * @return string highlight
7869       */
7870      public function output_html($data, $query='') {
7871          global $CFG, $OUTPUT;
7872          $return = '';
7873          $return = $OUTPUT->heading(new lang_string('customfields', 'core_customfield'), 3, 'main');
7874          $return .= $OUTPUT->box_start('generalbox customfieldsui');
7875  
7876          $fields = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7877  
7878          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down'));
7879          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7880          $txt->updown = "$txt->up/$txt->down";
7881  
7882          $table = new html_table();
7883          $table->head  = array($txt->name, $txt->enable, $txt->uninstall, $txt->settings);
7884          $table->align = array('left', 'center', 'center', 'center');
7885          $table->attributes['class'] = 'managecustomfieldtable generaltable admintable';
7886          $table->data  = array();
7887  
7888          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7889          foreach ($fields as $field) {
7890              $url = new moodle_url('/admin/customfields.php',
7891                      array('sesskey' => sesskey(), 'field' => $field->name));
7892  
7893              if ($field->is_enabled()) {
7894                  $strfieldname = $field->displayname;
7895                  $class = '';
7896                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7897                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7898              } else {
7899                  $strfieldname = $field->displayname;
7900                  $class = 'dimmed_text';
7901                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7902                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7903              }
7904              $settings = '';
7905              if ($field->get_settings_url()) {
7906                  $settings = html_writer::link($field->get_settings_url(), $txt->settings);
7907              }
7908              $uninstall = '';
7909              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('customfield_'.$field->name, 'manage')) {
7910                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7911              }
7912              $row = new html_table_row(array($strfieldname, $hideshow, $uninstall, $settings));
7913              $row->attributes['class'] = $class;
7914              $table->data[] = $row;
7915          }
7916          $return .= html_writer::table($table);
7917          $return .= $OUTPUT->box_end();
7918          return highlight($query, $return);
7919      }
7920  }
7921  
7922  /**
7923   * Data formats manager. Allow reorder and to enable/disable data formats and jump to settings
7924   *
7925   * @copyright  2016 Brendan Heywood (brendan@catalyst-au.net)
7926   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7927   */
7928  class admin_setting_managedataformats extends admin_setting {
7929  
7930      /**
7931       * Calls parent::__construct with specific arguments
7932       */
7933      public function __construct() {
7934          $this->nosave = true;
7935          parent::__construct('managedataformats', new lang_string('managedataformats'), '', '');
7936      }
7937  
7938      /**
7939       * Always returns true
7940       *
7941       * @return true
7942       */
7943      public function get_setting() {
7944          return true;
7945      }
7946  
7947      /**
7948       * Always returns true
7949       *
7950       * @return true
7951       */
7952      public function get_defaultsetting() {
7953          return true;
7954      }
7955  
7956      /**
7957       * Always returns '' and doesn't write anything
7958       *
7959       * @param mixed $data string or array, must not be NULL
7960       * @return string Always returns ''
7961       */
7962      public function write_setting($data) {
7963          // Do not write any setting.
7964          return '';
7965      }
7966  
7967      /**
7968       * Search to find if Query is related to format plugin
7969       *
7970       * @param string $query The string to search for
7971       * @return bool true for related false for not
7972       */
7973      public function is_related($query) {
7974          if (parent::is_related($query)) {
7975              return true;
7976          }
7977          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
7978          foreach ($formats as $format) {
7979              if (strpos($format->component, $query) !== false ||
7980                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7981                  return true;
7982              }
7983          }
7984          return false;
7985      }
7986  
7987      /**
7988       * Return XHTML to display control
7989       *
7990       * @param mixed $data Unused
7991       * @param string $query
7992       * @return string highlight
7993       */
7994      public function output_html($data, $query='') {
7995          global $CFG, $OUTPUT;
7996          $return = '';
7997  
7998          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
7999  
8000          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
8001          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
8002          $txt->updown = "$txt->up/$txt->down";
8003  
8004          $table = new html_table();
8005          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
8006          $table->align = array('left', 'center', 'center', 'center', 'center');
8007          $table->attributes['class'] = 'manageformattable generaltable admintable';
8008          $table->data  = array();
8009  
8010          $cnt = 0;
8011          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8012          $totalenabled = 0;
8013          foreach ($formats as $format) {
8014              if ($format->is_enabled() && $format->is_installed_and_upgraded()) {
8015                  $totalenabled++;
8016              }
8017          }
8018          foreach ($formats as $format) {
8019              $status = $format->get_status();
8020              $url = new moodle_url('/admin/dataformats.php',
8021                      array('sesskey' => sesskey(), 'name' => $format->name));
8022  
8023              $class = '';
8024              if ($format->is_enabled()) {
8025                  $strformatname = $format->displayname;
8026                  if ($totalenabled == 1&& $format->is_enabled()) {
8027                      $hideshow = '';
8028                  } else {
8029                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
8030                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
8031                  }
8032              } else {
8033                  $class = 'dimmed_text';
8034                  $strformatname = $format->displayname;
8035                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
8036                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
8037              }
8038  
8039              $updown = '';
8040              if ($cnt) {
8041                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
8042                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
8043              } else {
8044                  $updown .= $spacer;
8045              }
8046              if ($cnt < count($formats) - 1) {
8047                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
8048                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
8049              } else {
8050                  $updown .= $spacer;
8051              }
8052  
8053              $uninstall = '';
8054              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
8055                  $uninstall = get_string('status_missing', 'core_plugin');
8056              } else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
8057                  $uninstall = get_string('status_new', 'core_plugin');
8058              } else if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('dataformat_'.$format->name, 'manage')) {
8059                  if ($totalenabled != 1 || !$format->is_enabled()) {
8060                      $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
8061                  }
8062              }
8063  
8064              $settings = '';
8065              if ($format->get_settings_url()) {
8066                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
8067              }
8068  
8069              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
8070              if ($class) {
8071                  $row->attributes['class'] = $class;
8072              }
8073              $table->data[] = $row;
8074              $cnt++;
8075          }
8076          $return .= html_writer::table($table);
8077          return highlight($query, $return);
8078      }
8079  }
8080  
8081  /**
8082   * Special class for filter administration.
8083   *
8084   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8085   */
8086  class admin_page_managefilters extends admin_externalpage {
8087      /**
8088       * Calls parent::__construct with specific arguments
8089       */
8090      public function __construct() {
8091          global $CFG;
8092          parent::__construct('managefilters', get_string('filtersettings', 'admin'), "$CFG->wwwroot/$CFG->admin/filters.php");
8093      }
8094  
8095      /**
8096       * Searches all installed filters for specified filter
8097       *
8098       * @param string $query The filter(string) to search for
8099       * @param string $query
8100       */
8101      public function search($query) {
8102          global $CFG;
8103          if ($result = parent::search($query)) {
8104              return $result;
8105          }
8106  
8107          $found = false;
8108          $filternames = filter_get_all_installed();
8109          foreach ($filternames as $path => $strfiltername) {
8110              if (strpos(core_text::strtolower($strfiltername), $query) !== false) {
8111                  $found = true;
8112                  break;
8113              }
8114              if (strpos($path, $query) !== false) {
8115                  $found = true;
8116                  break;
8117              }
8118          }
8119  
8120          if ($found) {
8121              $result = new stdClass;
8122              $result->page = $this;
8123              $result->settings = array();
8124              return array($this->name => $result);
8125          } else {
8126              return array();
8127          }
8128      }
8129  }
8130  
8131  /**
8132   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8133   * Requires a get_rank method on the plugininfo class for sorting.
8134   *
8135   * @copyright 2017 Damyon Wiese
8136   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8137   */
8138  abstract class admin_setting_manage_plugins extends admin_setting {
8139  
8140      /**
8141       * Get the admin settings section name (just a unique string)
8142       *
8143       * @return string
8144       */
8145      public function get_section_name() {
8146          return 'manage' . $this->get_plugin_type() . 'plugins';
8147      }
8148  
8149      /**
8150       * Get the admin settings section title (use get_string).
8151       *
8152       * @return string
8153       */
8154      abstract public function get_section_title();
8155  
8156      /**
8157       * Get the type of plugin to manage.
8158       *
8159       * @return string
8160       */
8161      abstract public function get_plugin_type();
8162  
8163      /**
8164       * Get the name of the second column.
8165       *
8166       * @return string
8167       */
8168      public function get_info_column_name() {
8169          return '';
8170      }
8171  
8172      /**
8173       * Get the type of plugin to manage.
8174       *
8175       * @param plugininfo The plugin info class.
8176       * @return string
8177       */
8178      abstract public function get_info_column($plugininfo);
8179  
8180      /**
8181       * Calls parent::__construct with specific arguments
8182       */
8183      public function __construct() {
8184          $this->nosave = true;
8185          parent::__construct($this->get_section_name(), $this->get_section_title(), '', '');
8186      }
8187  
8188      /**
8189       * Always returns true, does nothing
8190       *
8191       * @return true
8192       */
8193      public function get_setting() {
8194          return true;
8195      }
8196  
8197      /**
8198       * Always returns true, does nothing
8199       *
8200       * @return true
8201       */
8202      public function get_defaultsetting() {
8203          return true;
8204      }
8205  
8206      /**
8207       * Always returns '', does not write anything
8208       *
8209       * @param mixed $data
8210       * @return string Always returns ''
8211       */
8212      public function write_setting($data) {
8213          // Do not write any setting.
8214          return '';
8215      }
8216  
8217      /**
8218       * Checks if $query is one of the available plugins of this type
8219       *
8220       * @param string $query The string to search for
8221       * @return bool Returns true if found, false if not
8222       */
8223      public function is_related($query) {
8224          if (parent::is_related($query)) {
8225              return true;
8226          }
8227  
8228          $query = core_text::strtolower($query);
8229          $plugins = core_plugin_manager::instance()->get_plugins_of_type($this->get_plugin_type());
8230          foreach ($plugins as $name => $plugin) {
8231              $localised = $plugin->displayname;
8232              if (strpos(core_text::strtolower($name), $query) !== false) {
8233                  return true;
8234              }
8235              if (strpos(core_text::strtolower($localised), $query) !== false) {
8236                  return true;
8237              }
8238          }
8239          return false;
8240      }
8241  
8242      /**
8243       * The URL for the management page for this plugintype.
8244       *
8245       * @return moodle_url
8246       */
8247      protected function get_manage_url() {
8248          return new moodle_url('/admin/updatesetting.php');
8249      }
8250  
8251      /**
8252       * Builds the HTML to display the control.
8253       *
8254       * @param string $data Unused
8255       * @param string $query
8256       * @return string
8257       */
8258      public function output_html($data, $query = '') {
8259          global $CFG, $OUTPUT, $DB, $PAGE;
8260  
8261          $context = (object) [
8262              'manageurl' => new moodle_url($this->get_manage_url(), [
8263                      'type' => $this->get_plugin_type(),
8264                      'sesskey' => sesskey(),
8265                  ]),
8266              'infocolumnname' => $this->get_info_column_name(),
8267              'plugins' => [],
8268          ];
8269  
8270          $pluginmanager = core_plugin_manager::instance();
8271          $allplugins = $pluginmanager->get_plugins_of_type($this->get_plugin_type());
8272          $enabled = $pluginmanager->get_enabled_plugins($this->get_plugin_type());
8273          $plugins = array_merge($enabled, $allplugins);
8274          foreach ($plugins as $key => $plugin) {
8275              $pluginlink = new moodle_url($context->manageurl, ['plugin' => $key]);
8276  
8277              $pluginkey = (object) [
8278                  'plugin' => $plugin->displayname,
8279                  'enabled' => $plugin->is_enabled(),
8280                  'togglelink' => '',
8281                  'moveuplink' => '',
8282                  'movedownlink' => '',
8283                  'settingslink' => $plugin->get_settings_url(),
8284                  'uninstalllink' => '',
8285                  'info' => '',
8286              ];
8287  
8288              // Enable/Disable link.
8289              $togglelink = new moodle_url($pluginlink);
8290              if ($plugin->is_enabled()) {
8291                  $toggletarget = false;
8292                  $togglelink->param('action', 'disable');
8293  
8294                  if (count($context->plugins)) {
8295                      // This is not the first plugin.
8296                      $pluginkey->moveuplink = new moodle_url($pluginlink, ['action' => 'up']);
8297                  }
8298  
8299                  if (count($enabled) > count($context->plugins) + 1) {
8300                      // This is not the last plugin.
8301                      $pluginkey->movedownlink = new moodle_url($pluginlink, ['action' => 'down']);
8302                  }
8303  
8304                  $pluginkey->info = $this->get_info_column($plugin);
8305              } else {
8306                  $toggletarget = true;
8307                  $togglelink->param('action', 'enable');
8308              }
8309  
8310              $pluginkey->toggletarget = $toggletarget;
8311              $pluginkey->togglelink = $togglelink;
8312  
8313              $frankenstyle = $plugin->type . '_' . $plugin->name;
8314              if ($uninstalllink = core_plugin_manager::instance()->get_uninstall_url($frankenstyle, 'manage')) {
8315                  // This plugin supports uninstallation.
8316                  $pluginkey->uninstalllink = $uninstalllink;
8317              }
8318  
8319              if (!empty($this->get_info_column_name())) {
8320                  // This plugintype has an info column.
8321                  $pluginkey->info = $this->get_info_column($plugin);
8322              }
8323  
8324              $context->plugins[] = $pluginkey;
8325          }
8326  
8327          $str = $OUTPUT->render_from_template('core_admin/setting_manage_plugins', $context);
8328          return highlight($query, $str);
8329      }
8330  }
8331  
8332  /**
8333   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8334   * Requires a get_rank method on the plugininfo class for sorting.
8335   *
8336   * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
8337  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8338   */
8339  class admin_setting_manage_fileconverter_plugins extends admin_setting_manage_plugins {
8340      public function get_section_title() {
8341          return get_string('type_fileconverter_plural', 'plugin');
8342      }
8343  
8344      public function get_plugin_type() {
8345          return 'fileconverter';
8346      }
8347  
8348      public function get_info_column_name() {
8349          return get_string('supportedconversions', 'plugin');
8350      }
8351  
8352      public function get_info_column($plugininfo) {
8353          return $plugininfo->get_supported_conversions();
8354      }
8355  }
8356  
8357  /**
8358   * Special class for media player plugins management.
8359   *
8360   * @copyright 2016 Marina Glancy
8361   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8362   */
8363  class admin_setting_managemediaplayers extends admin_setting {
8364      /**
8365       * Calls parent::__construct with specific arguments
8366       */
8367      public function __construct() {
8368          $this->nosave = true;
8369          parent::__construct('managemediaplayers', get_string('managemediaplayers', 'media'), '', '');
8370      }
8371  
8372      /**
8373       * Always returns true, does nothing
8374       *
8375       * @return true
8376       */
8377      public function get_setting() {
8378          return true;
8379      }
8380  
8381      /**
8382       * Always returns true, does nothing
8383       *
8384       * @return true
8385       */
8386      public function get_defaultsetting() {
8387          return true;
8388      }
8389  
8390      /**
8391       * Always returns '', does not write anything
8392       *
8393       * @param mixed $data
8394       * @return string Always returns ''
8395       */
8396      public function write_setting($data) {
8397          // Do not write any setting.
8398          return '';
8399      }
8400  
8401      /**
8402       * Checks if $query is one of the available enrol plugins
8403       *
8404       * @param string $query The string to search for
8405       * @return bool Returns true if found, false if not
8406       */
8407      public function is_related($query) {
8408          if (parent::is_related($query)) {
8409              return true;
8410          }
8411  
8412          $query = core_text::strtolower($query);
8413          $plugins = core_plugin_manager::instance()->get_plugins_of_type('media');
8414          foreach ($plugins as $name => $plugin) {
8415              $localised = $plugin->displayname;
8416              if (strpos(core_text::strtolower($name), $query) !== false) {
8417                  return true;
8418              }
8419              if (strpos(core_text::strtolower($localised), $query) !== false) {
8420                  return true;
8421              }
8422          }
8423          return false;
8424      }
8425  
8426      /**
8427       * Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8428       * @return \core\plugininfo\media[]
8429       */
8430      protected function get_sorted_plugins() {
8431          $pluginmanager = core_plugin_manager::instance();
8432  
8433          $plugins = $pluginmanager->get_plugins_of_type('media');
8434          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8435  
8436          // Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8437          \core_collator::asort_objects_by_method($plugins, 'get_rank', \core_collator::SORT_NUMERIC);
8438  
8439          $order = array_values($enabledplugins);
8440          $order = array_merge($order, array_diff(array_reverse(array_keys($plugins)), $order));
8441  
8442          $sortedplugins = array();
8443          foreach ($order as $name) {
8444              $sortedplugins[$name] = $plugins[$name];
8445          }
8446  
8447          return $sortedplugins;
8448      }
8449  
8450      /**
8451       * Builds the XHTML to display the control
8452       *
8453       * @param string $data Unused
8454       * @param string $query
8455       * @return string
8456       */
8457      public function output_html($data, $query='') {
8458          global $CFG, $OUTPUT, $DB, $PAGE;
8459  
8460          // Display strings.
8461          $strup        = get_string('up');
8462          $strdown      = get_string('down');
8463          $strsettings  = get_string('settings');
8464          $strenable    = get_string('enable');
8465          $strdisable   = get_string('disable');
8466          $struninstall = get_string('uninstallplugin', 'core_admin');
8467          $strversion   = get_string('version');
8468          $strname      = get_string('name');
8469          $strsupports  = get_string('supports', 'core_media');
8470  
8471          $pluginmanager = core_plugin_manager::instance();
8472  
8473          $plugins = $this->get_sorted_plugins();
8474          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8475  
8476          $return = $OUTPUT->box_start('generalbox mediaplayersui');
8477  
8478          $table = new html_table();
8479          $table->head  = array($strname, $strsupports, $strversion,
8480              $strenable, $strup.'/'.$strdown, $strsettings, $struninstall);
8481          $table->colclasses = array('leftalign', 'leftalign', 'centeralign',
8482              'centeralign', 'centeralign', 'centeralign', 'centeralign');
8483          $table->id = 'mediaplayerplugins';
8484          $table->attributes['class'] = 'admintable generaltable';
8485          $table->data  = array();
8486  
8487          // Iterate through media plugins and add to the display table.
8488          $updowncount = 1;
8489          $url = new moodle_url('/admin/media.php', array('sesskey' => sesskey()));
8490          $printed = array();
8491          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8492  
8493          $usedextensions = [];
8494          foreach ($plugins as $name => $plugin) {
8495              $url->param('media', $name);
8496              $plugininfo = $pluginmanager->get_plugin_info('media_'.$name);
8497              $version = $plugininfo->versiondb;
8498              $supports = $plugininfo->supports($usedextensions);
8499  
8500              // Hide/show links.
8501              $class = '';
8502              if (!$plugininfo->is_installed_and_upgraded()) {
8503                  $hideshow = '';
8504                  $enabled = false;
8505                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
8506              } else {
8507                  $enabled = $plugininfo->is_enabled();
8508                  if ($enabled) {
8509                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'disable')),
8510                          $OUTPUT->pix_icon('t/hide', $strdisable, 'moodle', array('class' => 'iconsmall')));
8511                  } else {
8512                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'enable')),
8513                          $OUTPUT->pix_icon('t/show', $strenable, 'moodle', array('class' => 'iconsmall')));
8514                      $class = 'dimmed_text';
8515                  }
8516                  $displayname = $plugin->displayname;
8517                  if (get_string_manager()->string_exists('pluginname_help', 'media_' . $name)) {
8518                      $displayname .= '&nbsp;' . $OUTPUT->help_icon('pluginname', 'media_' . $name);
8519                  }
8520              }
8521              if ($PAGE->theme->resolve_image_location('icon', 'media_' . $name, false)) {
8522                  $icon = $OUTPUT->pix_icon('icon', '', 'media_' . $name, array('class' => 'icon pluginicon'));
8523              } else {
8524                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
8525              }
8526  
8527              // Up/down link (only if enrol is enabled).
8528              $updown = '';
8529              if ($enabled) {
8530                  if ($updowncount > 1) {
8531                      $updown = html_writer::link(new moodle_url($url, array('action' => 'up')),
8532                          $OUTPUT->pix_icon('t/up', $strup, 'moodle', array('class' => 'iconsmall')));
8533                  } else {
8534                      $updown = $spacer;
8535                  }
8536                  if ($updowncount < count($enabledplugins)) {
8537                      $updown .= html_writer::link(new moodle_url($url, array('action' => 'down')),
8538                          $OUTPUT->pix_icon('t/down', $strdown, 'moodle', array('class' => 'iconsmall')));
8539                  } else {
8540                      $updown .= $spacer;
8541                  }
8542                  ++$updowncount;
8543              }
8544  
8545              $uninstall = '';
8546              $status = $plugininfo->get_status();
8547              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
8548                  $uninstall = get_string('status_missing', 'core_plugin') . '<br/>';
8549              }
8550              if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
8551                  $uninstall = get_string('status_new', 'core_plugin');
8552              } else if ($uninstallurl = $pluginmanager->get_uninstall_url('media_'.$name, 'manage')) {
8553                  $uninstall .= html_writer::link($uninstallurl, $struninstall);
8554              }
8555  
8556              $settings = '';
8557              if ($plugininfo->get_settings_url()) {
8558                  $settings = html_writer::link($plugininfo->get_settings_url(), $strsettings);
8559              }
8560  
8561              // Add a row to the table.
8562              $row = new html_table_row(array($icon.$displayname, $supports, $version, $hideshow, $updown, $settings, $uninstall));
8563              if ($class) {
8564                  $row->attributes['class'] = $class;
8565              }
8566              $table->data[] = $row;
8567  
8568              $printed[$name] = true;
8569          }
8570  
8571          $return .= html_writer::table($table);
8572          $return .= $OUTPUT->box_end();
8573          return highlight($query, $return);
8574      }
8575  }
8576  
8577  
8578  /**
8579   * Content bank content types manager. Allow reorder and to enable/disable content bank content types and jump to settings
8580   *
8581   * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
8582   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8583   */
8584  class admin_setting_managecontentbankcontenttypes extends admin_setting {
8585  
8586      /**
8587       * Calls parent::__construct with specific arguments
8588       */
8589      public function __construct() {
8590          $this->nosave = true;
8591          parent::__construct('contentbank', new lang_string('managecontentbanktypes'), '', '');
8592      }
8593  
8594      /**
8595       * Always returns true
8596       *
8597       * @return true
8598       */
8599      public function get_setting() {
8600          return true;
8601      }
8602  
8603      /**
8604       * Always returns true
8605       *
8606       * @return true
8607       */
8608      public function get_defaultsetting() {
8609          return true;
8610      }
8611  
8612      /**
8613       * Always returns '' and doesn't write anything
8614       *
8615       * @param mixed $data string or array, must not be NULL
8616       * @return string Always returns ''
8617       */
8618      public function write_setting($data) {
8619          // Do not write any setting.
8620          return '';
8621      }
8622  
8623      /**
8624       * Search to find if Query is related to content bank plugin
8625       *
8626       * @param string $query The string to search for
8627       * @return bool true for related false for not
8628       */
8629      public function is_related($query) {
8630          if (parent::is_related($query)) {
8631              return true;
8632          }
8633          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8634          foreach ($types as $type) {
8635              if (strpos($type->component, $query) !== false ||
8636                  strpos(core_text::strtolower($type->displayname), $query) !== false) {
8637                  return true;
8638              }
8639          }
8640          return false;
8641      }
8642  
8643      /**
8644       * Return XHTML to display control
8645       *
8646       * @param mixed $data Unused
8647       * @param string $query
8648       * @return string highlight
8649       */
8650      public function output_html($data, $query='') {
8651          global $CFG, $OUTPUT;
8652          $return = '';
8653  
8654          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8655          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'order', 'up', 'down', 'default'));
8656          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
8657  
8658          $table = new html_table();
8659          $table->head  = array($txt->name, $txt->enable, $txt->order, $txt->settings, $txt->uninstall);
8660          $table->align = array('left', 'center', 'center', 'center', 'center');
8661          $table->attributes['class'] = 'managecontentbanktable generaltable admintable';
8662          $table->data  = array();
8663          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8664  
8665          $totalenabled = 0;
8666          $count = 0;
8667          foreach ($types as $type) {
8668              if ($type->is_enabled() && $type->is_installed_and_upgraded()) {
8669                  $totalenabled++;
8670              }
8671          }
8672  
8673          foreach ($types as $type) {
8674              $url = new moodle_url('/admin/contentbank.php',
8675                  array('sesskey' => sesskey(), 'name' => $type->name));
8676  
8677              $class = '';
8678              $strtypename = $type->displayname;
8679              if ($type->is_enabled()) {
8680                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
8681                      $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
8682              } else {
8683                  $class = 'dimmed_text';
8684                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
8685                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
8686              }
8687  
8688              $updown = '';
8689              if ($count) {
8690                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
8691                          $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
8692              } else {
8693                  $updown .= $spacer;
8694              }
8695              if ($count < count($types) - 1) {
8696                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
8697                          $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
8698              } else {
8699                  $updown .= $spacer;
8700              }
8701  
8702              $settings = '';
8703              if ($type->get_settings_url()) {
8704                  $settings = html_writer::link($type->get_settings_url(), $txt->settings);
8705              }
8706  
8707              $uninstall = '';
8708              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('contenttype_'.$type->name, 'manage')) {
8709                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
8710              }
8711  
8712              $row = new html_table_row(array($strtypename, $hideshow, $updown, $settings, $uninstall));
8713              if ($class) {
8714                  $row->attributes['class'] = $class;
8715              }
8716              $table->data[] = $row;
8717              $count++;
8718          }
8719          $return .= html_writer::table($table);
8720          return highlight($query, $return);
8721      }
8722  }
8723  
8724  /**
8725   * Initialise admin page - this function does require login and permission
8726   * checks specified in page definition.
8727   *
8728   * This function must be called on each admin page before other code.
8729   *
8730   * @global moodle_page $PAGE
8731   *
8732   * @param string $section name of page
8733   * @param string $extrabutton extra HTML that is added after the blocks editing on/off button.
8734   * @param array $extraurlparams an array paramname => paramvalue, or parameters that need to be
8735   *      added to the turn blocks editing on/off form, so this page reloads correctly.
8736   * @param string $actualurl if the actual page being viewed is not the normal one for this
8737   *      page (e.g. admin/roles/allow.php, instead of admin/roles/manage.php, you can pass the alternate URL here.
8738   * @param array $options Additional options that can be specified for page setup.
8739   *      pagelayout - This option can be used to set a specific pagelyaout, admin is default.
8740   *      nosearch - Do not display search bar
8741   */
8742  function admin_externalpage_setup($section, $extrabutton = '', array $extraurlparams = null, $actualurl = '', array $options = array()) {
8743      global $CFG, $PAGE, $USER, $SITE, $OUTPUT;
8744  
8745      $PAGE->set_context(null); // hack - set context to something, by default to system context
8746  
8747      $site = get_site();
8748      require_login(null, false);
8749  
8750      if (!empty($options['pagelayout'])) {
8751          // A specific page layout has been requested.
8752          $PAGE->set_pagelayout($options['pagelayout']);
8753      } else if ($section === 'upgradesettings') {
8754          $PAGE->set_pagelayout('maintenance');
8755      } else {
8756          $PAGE->set_pagelayout('admin');
8757      }
8758  
8759      $adminroot = admin_get_root(false, false); // settings not required for external pages
8760      $extpage = $adminroot->locate($section, true);
8761  
8762      $hassiteconfig = has_capability('moodle/site:config', context_system::instance());
8763      if (empty($extpage) or !($extpage instanceof admin_externalpage)) {
8764          // The requested section isn't in the admin tree
8765          // It could be because the user has inadequate capapbilities or because the section doesn't exist
8766          if (!$hassiteconfig) {
8767              // The requested section could depend on a different capability
8768              // but most likely the user has inadequate capabilities
8769              throw new \moodle_exception('accessdenied', 'admin');
8770          } else {
8771              throw new \moodle_exception('sectionerror', 'admin', "$CFG->wwwroot/$CFG->admin/");
8772          }
8773      }
8774  
8775      // this eliminates our need to authenticate on the actual pages
8776      if (!$extpage->check_access()) {
8777          throw new \moodle_exception('accessdenied', 'admin');
8778          die;
8779      }
8780  
8781      navigation_node::require_admin_tree();
8782  
8783      // $PAGE->set_extra_button($extrabutton); TODO
8784  
8785      if (!$actualurl) {
8786          $actualurl = $extpage->url;
8787      }
8788  
8789      $PAGE->set_url($actualurl, $extraurlparams);
8790      if (strpos($PAGE->pagetype, 'admin-') !== 0) {
8791          $PAGE->set_pagetype('admin-' . $PAGE->pagetype);
8792      }
8793  
8794      if (empty($SITE->fullname) || empty($SITE->shortname)) {
8795          // During initial install.
8796          $strinstallation = get_string('installation', 'install');
8797          $strsettings = get_string('settings');
8798          $PAGE->navbar->add($strsettings);
8799          $PAGE->set_title($strinstallation);
8800          $PAGE->set_heading($strinstallation);
8801          $PAGE->set_cacheable(false);
8802          return;
8803      }
8804  
8805      // Locate the current item on the navigation and make it active when found.
8806      $path = $extpage->path;
8807      $node = $PAGE->settingsnav;
8808      while ($node && count($path) > 0) {
8809          $node = $node->get(array_pop($path));
8810      }
8811      if ($node) {
8812          $node->make_active();
8813      }
8814  
8815      // Normal case.
8816      $adminediting = optional_param('adminedit', -1, PARAM_BOOL);
8817      if ($PAGE->user_allowed_editing() && $adminediting != -1) {
8818          $USER->editing = $adminediting;
8819      }
8820  
8821      if ($PAGE->user_allowed_editing() && !$PAGE->theme->haseditswitch) {
8822          if ($PAGE->user_is_editing()) {
8823              $caption = get_string('blockseditoff');
8824              $url = new moodle_url($PAGE->url, array('adminedit'=>'0', 'sesskey'=>sesskey()));
8825          } else {
8826              $caption = get_string('blocksediton');
8827              $url = new moodle_url($PAGE->url, array('adminedit'=>'1', 'sesskey'=>sesskey()));
8828          }
8829          $PAGE->set_button($OUTPUT->single_button($url, $caption, 'get'));
8830      }
8831  
8832      $PAGE->set_title(implode(moodle_page::TITLE_SEPARATOR, $extpage->visiblepath));
8833      $PAGE->set_heading($SITE->fullname);
8834  
8835      if ($hassiteconfig && empty($options['nosearch'])) {
8836          $PAGE->add_header_action($OUTPUT->render_from_template('core_admin/header_search_input', [
8837              'action' => new moodle_url('/admin/search.php'),
8838              'query' => $PAGE->url->get_param('query'),
8839          ]));
8840      }
8841  
8842      // prevent caching in nav block
8843      $PAGE->navigation->clear_cache();
8844  }
8845  
8846  /**
8847   * Returns the reference to admin tree root
8848   *
8849   * @return object admin_root object
8850   */
8851  function admin_get_root($reload=false, $requirefulltree=true) {
8852      global $CFG, $DB, $OUTPUT, $ADMIN;
8853  
8854      if (is_null($ADMIN)) {
8855      // create the admin tree!
8856          $ADMIN = new admin_root($requirefulltree);
8857      }
8858  
8859      if ($reload or ($requirefulltree and !$ADMIN->fulltree)) {
8860          $ADMIN->purge_children($requirefulltree);
8861      }
8862  
8863      if (!$ADMIN->loaded) {
8864      // we process this file first to create categories first and in correct order
8865          require($CFG->dirroot.'/'.$CFG->admin.'/settings/top.php');
8866  
8867          // now we process all other files in admin/settings to build the admin tree
8868          foreach (glob($CFG->dirroot.'/'.$CFG->admin.'/settings/*.php') as $file) {
8869              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/top.php') {
8870                  continue;
8871              }
8872              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php') {
8873              // plugins are loaded last - they may insert pages anywhere
8874                  continue;
8875              }
8876              require($file);
8877          }
8878          require($CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php');
8879  
8880          $ADMIN->loaded = true;
8881      }
8882  
8883      return $ADMIN;
8884  }
8885  
8886  /// settings utility functions
8887  
8888  /**
8889   * This function applies default settings.
8890   * Because setting the defaults of some settings can enable other settings,
8891   * this function is called recursively until no more new settings are found.
8892   *
8893   * @param object $node, NULL means complete tree, null by default
8894   * @param bool $unconditional if true overrides all values with defaults, true by default
8895   * @param array $admindefaultsettings default admin settings to apply. Used recursively
8896   * @param array $settingsoutput The names and values of the changed settings. Used recursively
8897   * @return array $settingsoutput The names and values of the changed settings
8898   */
8899  function admin_apply_default_settings($node=null, $unconditional=true, $admindefaultsettings=array(), $settingsoutput=array()) {
8900      $counter = 0;
8901  
8902      if (is_null($node)) {
8903          core_plugin_manager::reset_caches();
8904          $node = admin_get_root(true, true);
8905          $counter = count($settingsoutput);
8906      }
8907  
8908      if ($node instanceof admin_category) {
8909          $entries = array_keys($node->children);
8910          foreach ($entries as $entry) {
8911              $settingsoutput = admin_apply_default_settings(
8912                      $node->children[$entry], $unconditional, $admindefaultsettings, $settingsoutput
8913                      );
8914          }
8915  
8916      } else if ($node instanceof admin_settingpage) {
8917          foreach ($node->settings as $setting) {
8918              if ($setting->nosave) {
8919                  // Not a real setting, must be a heading or description.
8920                  continue;
8921              }
8922              if (!$unconditional && !is_null($setting->get_setting())) {
8923                  // Do not override existing defaults.
8924                  continue;
8925              }
8926              $defaultsetting = $setting->get_defaultsetting();
8927              if (is_null($defaultsetting)) {
8928                  // No value yet - default maybe applied after admin user creation or in upgradesettings.
8929                  continue;
8930              }
8931  
8932              $settingname = $node->name . '_' . $setting->name; // Get a unique name for the setting.
8933  
8934              if (!array_key_exists($settingname, $admindefaultsettings)) {  // Only update a setting if not already processed.
8935                  $admindefaultsettings[$settingname] = $settingname;
8936                  $settingsoutput[$settingname] = $defaultsetting;
8937  
8938                  // Set the default for this setting.
8939                  $setting->write_setting($defaultsetting);
8940                  $setting->write_setting_flags(null);
8941              } else {
8942                  unset($admindefaultsettings[$settingname]); // Remove processed settings.
8943              }
8944          }
8945      }
8946  
8947      // Call this function recursively until all settings are processed.
8948      if (($node instanceof admin_root) && ($counter != count($settingsoutput))) {
8949          $settingsoutput = admin_apply_default_settings(null, $unconditional, $admindefaultsettings, $settingsoutput);
8950      }
8951      // Just in case somebody modifies the list of active plugins directly.
8952      core_plugin_manager::reset_caches();
8953  
8954      return $settingsoutput;
8955  }
8956  
8957  /**
8958   * Store changed settings, this function updates the errors variable in $ADMIN
8959   *
8960   * @param object $formdata from form
8961   * @return int number of changed settings
8962   */
8963  function admin_write_settings($formdata) {
8964      global $CFG, $SITE, $DB;
8965  
8966      $olddbsessions = !empty($CFG->dbsessions);
8967      $formdata = (array)$formdata;
8968  
8969      $data = array();
8970      foreach ($formdata as $fullname=>$value) {
8971          if (strpos($fullname, 's_') !== 0) {
8972              continue; // not a config value
8973          }
8974          $data[$fullname] = $value;
8975      }
8976  
8977      $adminroot = admin_get_root();
8978      $settings = admin_find_write_settings($adminroot, $data);
8979  
8980      $count = 0;
8981      foreach ($settings as $fullname=>$setting) {
8982          /** @var $setting admin_setting */
8983          $original = $setting->get_setting();
8984          $error = $setting->write_setting($data[$fullname]);
8985          if ($error !== '') {
8986              $adminroot->errors[$fullname] = new stdClass();
8987              $adminroot->errors[$fullname]->data  = $data[$fullname];
8988              $adminroot->errors[$fullname]->id    = $setting->get_id();
8989              $adminroot->errors[$fullname]->error = $error;
8990          } else {
8991              $setting->write_setting_flags($data);
8992          }
8993          if ($setting->post_write_settings($original)) {
8994              $count++;
8995          }
8996      }
8997  
8998      if ($olddbsessions != !empty($CFG->dbsessions)) {
8999          require_logout();
9000      }
9001  
9002      // Now update $SITE - just update the fields, in case other people have a
9003      // a reference to it (e.g. $PAGE, $COURSE).
9004      $newsite = $DB->get_record('course', array('id'=>$SITE->id));
9005      foreach (get_object_vars($newsite) as $field => $value) {
9006          $SITE->$field = $value;
9007      }
9008  
9009      // now reload all settings - some of them might depend on the changed
9010      admin_get_root(true);
9011      return $count;
9012  }
9013  
9014  /**
9015   * Internal recursive function - finds all settings from submitted form
9016   *
9017   * @param object $node Instance of admin_category, or admin_settingpage
9018   * @param array $data
9019   * @return array
9020   */
9021  function admin_find_write_settings($node, $data) {
9022      $return = array();
9023  
9024      if (empty($data)) {
9025          return $return;
9026      }
9027  
9028      if ($node instanceof admin_category) {
9029          if ($node->check_access()) {
9030              $entries = array_keys($node->children);
9031              foreach ($entries as $entry) {
9032                  $return = array_merge($return, admin_find_write_settings($node->children[$entry], $data));
9033              }
9034          }
9035  
9036      } else if ($node instanceof admin_settingpage) {
9037          if ($node->check_access()) {
9038              foreach ($node->settings as $setting) {
9039                  $fullname = $setting->get_full_name();
9040                  if (array_key_exists($fullname, $data)) {
9041                      $return[$fullname] = $setting;
9042                  }
9043              }
9044          }
9045  
9046      }
9047  
9048      return $return;
9049  }
9050  
9051  /**
9052   * Internal function - prints the search results
9053   *
9054   * @param string $query String to search for
9055   * @return string empty or XHTML
9056   */
9057  function admin_search_settings_html($query) {
9058      global $CFG, $OUTPUT, $PAGE;
9059  
9060      if (core_text::strlen($query) < 2) {
9061          return '';
9062      }
9063      $query = core_text::strtolower($query);
9064  
9065      $adminroot = admin_get_root();
9066      $findings = $adminroot->search($query);
9067      $savebutton = false;
9068  
9069      $tpldata = (object) [
9070          'actionurl' => $PAGE->url->out(false),
9071          'results' => [],
9072          'sesskey' => sesskey(),
9073      ];
9074  
9075      foreach ($findings as $found) {
9076          $page     = $found->page;
9077          $settings = $found->settings;
9078          if ($page->is_hidden()) {
9079          // hidden pages are not displayed in search results
9080              continue;
9081          }
9082  
9083          $heading = highlight($query, $page->visiblename);
9084          $headingurl = null;
9085          if ($page instanceof admin_externalpage) {
9086              $headingurl = new moodle_url($page->url);
9087          } else if ($page instanceof admin_settingpage) {
9088              $headingurl = new moodle_url('/admin/settings.php', ['section' => $page->name]);
9089          } else {
9090              continue;
9091          }
9092  
9093          // Locate the page in the admin root and populate its visiblepath attribute.
9094          $path = array();
9095          $located = $adminroot->locate($page->name, true);
9096          if ($located) {
9097              foreach ($located->visiblepath as $pathitem) {
9098                  array_unshift($path, (string) $pathitem);
9099              }
9100          }
9101  
9102          $sectionsettings = [];
9103          if (!empty($settings)) {
9104              foreach ($settings as $setting) {
9105                  if (empty($setting->nosave)) {
9106                      $savebutton = true;
9107                  }
9108                  $fullname = $setting->get_full_name();
9109                  if (array_key_exists($fullname, $adminroot->errors)) {
9110                      $data = $adminroot->errors[$fullname]->data;
9111                  } else {
9112                      $data = $setting->get_setting();
9113                  // do not use defaults if settings not available - upgradesettings handles the defaults!
9114                  }
9115                  $sectionsettings[] = $setting->output_html($data, $query);
9116              }
9117          }
9118  
9119          $tpldata->results[] = (object) [
9120              'title' => $heading,
9121              'path' => $path,
9122              'url' => $headingurl->out(false),
9123              'settings' => $sectionsettings
9124          ];
9125      }
9126  
9127      $tpldata->showsave = $savebutton;
9128      $tpldata->hasresults = !empty($tpldata->results);
9129  
9130      return $OUTPUT->render_from_template('core_admin/settings_search_results', $tpldata);
9131  }
9132  
9133  /**
9134   * Internal function - returns arrays of html pages with uninitialised settings
9135   *
9136   * @param object $node Instance of admin_category or admin_settingpage
9137   * @return array
9138   */
9139  function admin_output_new_settings_by_page($node) {
9140      global $OUTPUT;
9141      $return = array();
9142  
9143      if ($node instanceof admin_category) {
9144          $entries = array_keys($node->children);
9145          foreach ($entries as $entry) {
9146              $return += admin_output_new_settings_by_page($node->children[$entry]);
9147          }
9148  
9149      } else if ($node instanceof admin_settingpage) {
9150              $newsettings = array();
9151              foreach ($node->settings as $setting) {
9152                  if (is_null($setting->get_setting())) {
9153                      $newsettings[] = $setting;
9154                  }
9155              }
9156              if (count($newsettings) > 0) {
9157                  $adminroot = admin_get_root();
9158                  $page = $OUTPUT->heading(get_string('upgradesettings','admin').' - '.$node->visiblename, 2, 'main');
9159                  $page .= '<fieldset class="adminsettings">'."\n";
9160                  foreach ($newsettings as $setting) {
9161                      $fullname = $setting->get_full_name();
9162                      if (array_key_exists($fullname, $adminroot->errors)) {
9163                          $data = $adminroot->errors[$fullname]->data;
9164                      } else {
9165                          $data = $setting->get_setting();
9166                          if (is_null($data)) {
9167                              $data = $setting->get_defaultsetting();
9168                          }
9169                      }
9170                      $page .= '<div class="clearer"><!-- --></div>'."\n";
9171                      $page .= $setting->output_html($data);
9172                  }
9173                  $page .= '</fieldset>';
9174                  $return[$node->name] = $page;
9175              }
9176          }
9177  
9178      return $return;
9179  }
9180  
9181  /**
9182   * Format admin settings
9183   *
9184   * @param object $setting
9185   * @param string $title label element
9186   * @param string $form form fragment, html code - not highlighted automatically
9187   * @param string $description
9188   * @param mixed $label link label to id, true by default or string being the label to connect it to
9189   * @param string $warning warning text
9190   * @param sting $defaultinfo defaults info, null means nothing, '' is converted to "Empty" string, defaults to null
9191   * @param string $query search query to be highlighted
9192   * @return string XHTML
9193   */
9194  function format_admin_setting($setting, $title='', $form='', $description='', $label=true, $warning='', $defaultinfo=NULL, $query='') {
9195      global $CFG, $OUTPUT;
9196  
9197      $context = (object) [
9198          'name' => empty($setting->plugin) ? $setting->name : "$setting->plugin | $setting->name",
9199          'fullname' => $setting->get_full_name(),
9200      ];
9201  
9202      // Sometimes the id is not id_s_name, but id_s_name_m or something, and this does not validate.
9203      if ($label === true) {
9204          $context->labelfor = $setting->get_id();
9205      } else if ($label === false) {
9206          $context->labelfor = '';
9207      } else {
9208          $context->labelfor = $label;
9209      }
9210  
9211      $form .= $setting->output_setting_flags();
9212  
9213      $context->warning = $warning;
9214      $context->override = '';
9215      if (empty($setting->plugin)) {
9216          if ($setting->is_forceable() && array_key_exists($setting->name, $CFG->config_php_settings)) {
9217              $context->override = get_string('configoverride', 'admin');
9218          }
9219      } else {
9220          if (array_key_exists($setting->plugin, $CFG->forced_plugin_settings) and array_key_exists($setting->name, $CFG->forced_plugin_settings[$setting->plugin])) {
9221              $context->override = get_string('configoverride', 'admin');
9222          }
9223      }
9224  
9225      $defaults = array();
9226      if (!is_null($defaultinfo)) {
9227          if ($defaultinfo === '') {
9228              $defaultinfo = get_string('emptysettingvalue', 'admin');
9229          }
9230          $defaults[] = $defaultinfo;
9231      }
9232  
9233      $context->default = null;
9234      $setting->get_setting_flag_defaults($defaults);
9235      if (!empty($defaults)) {
9236          $defaultinfo = implode(', ', $defaults);
9237          $defaultinfo = highlight($query, nl2br(s($defaultinfo)));
9238          $context->default = get_string('defaultsettinginfo', 'admin', $defaultinfo);
9239      }
9240  
9241  
9242      $context->error = '';
9243      $adminroot = admin_get_root();
9244      if (array_key_exists($context->fullname, $adminroot->errors)) {
9245          $context->error = $adminroot->errors[$context->fullname]->error;
9246      }
9247  
9248      if ($dependenton = $setting->get_dependent_on()) {
9249          $context->dependenton = get_string('settingdependenton', 'admin', implode(', ', $dependenton));
9250      }
9251  
9252      $context->id = 'admin-' . $setting->name;
9253      $context->title = highlightfast($query, $title);
9254      $context->name = highlightfast($query, $context->name);
9255      $context->description = highlight($query, markdown_to_html($description));
9256      $context->element = $form;
9257      $context->forceltr = $setting->get_force_ltr();
9258      $context->customcontrol = $setting->has_custom_form_control();
9259  
9260      return $OUTPUT->render_from_template('core_admin/setting', $context);
9261  }
9262  
9263  /**
9264   * Based on find_new_settings{@link ()}  in upgradesettings.php
9265   * Looks to find any admin settings that have not been initialized. Returns 1 if it finds any.
9266   *
9267   * @param object $node Instance of admin_category, or admin_settingpage
9268   * @return boolean true if any settings haven't been initialised, false if they all have
9269   */
9270  function any_new_admin_settings($node) {
9271  
9272      if ($node instanceof admin_category) {
9273          $entries = array_keys($node->children);
9274          foreach ($entries as $entry) {
9275              if (any_new_admin_settings($node->children[$entry])) {
9276                  return true;
9277              }
9278          }
9279  
9280      } else if ($node instanceof admin_settingpage) {
9281              foreach ($node->settings as $setting) {
9282                  if ($setting->get_setting() === NULL) {
9283                      return true;
9284                  }
9285              }
9286          }
9287  
9288      return false;
9289  }
9290  
9291  /**
9292   * Given a table and optionally a column name should replaces be done?
9293   *
9294   * @param string $table name
9295   * @param string $column name
9296   * @return bool success or fail
9297   */
9298  function db_should_replace($table, $column = '', $additionalskiptables = ''): bool {
9299  
9300      // TODO: this is horrible hack, we should have a hook and each plugin should be responsible for proper replacing...
9301      $skiptables = ['config', 'config_plugins', 'filter_config', 'sessions',
9302          'events_queue', 'repository_instance_config', 'block_instances', 'files'];
9303  
9304      // Additional skip tables.
9305      if (!empty($additionalskiptables)) {
9306          $skiptables = array_merge($skiptables, explode(',', str_replace(' ', '',  $additionalskiptables)));
9307      }
9308  
9309      // Don't process these.
9310      if (in_array($table, $skiptables)) {
9311          return false;
9312      }
9313  
9314      // To be safe never replace inside a table that looks related to logging.
9315      if (preg_match('/(^|_)logs?($|_)/', $table)) {
9316          return false;
9317      }
9318  
9319      // Do column based exclusions.
9320      if (!empty($column)) {
9321          // Don't touch anything that looks like a hash.
9322          if (preg_match('/hash$/', $column)) {
9323              return false;
9324          }
9325      }
9326  
9327      return true;
9328  }
9329  
9330  /**
9331   * Moved from admin/replace.php so that we can use this in cron
9332   *
9333   * @param string $search string to look for
9334   * @param string $replace string to replace
9335   * @return bool success or fail
9336   */
9337  function db_replace($search, $replace, $additionalskiptables = '') {
9338      global $DB, $CFG, $OUTPUT;
9339  
9340      // Turn off time limits, sometimes upgrades can be slow.
9341      core_php_time_limit::raise();
9342  
9343      if (!$tables = $DB->get_tables() ) {    // No tables yet at all.
9344          return false;
9345      }
9346      foreach ($tables as $table) {
9347  
9348          if (!db_should_replace($table, '', $additionalskiptables)) {
9349              continue;
9350          }
9351  
9352          if ($columns = $DB->get_columns($table)) {
9353              $DB->set_debug(true);
9354              foreach ($columns as $column) {
9355                  if (!db_should_replace($table, $column->name)) {
9356                      continue;
9357                  }
9358                  $DB->replace_all_text($table, $column, $search, $replace);
9359              }
9360              $DB->set_debug(false);
9361          }
9362      }
9363  
9364      // delete modinfo caches
9365      rebuild_course_cache(0, true);
9366  
9367      // TODO: we should ask all plugins to do the search&replace, for now let's do only blocks...
9368      $blocks = core_component::get_plugin_list('block');
9369      foreach ($blocks as $blockname=>$fullblock) {
9370          if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
9371              continue;
9372          }
9373  
9374          if (!is_readable($fullblock.'/lib.php')) {
9375              continue;
9376          }
9377  
9378          $function = 'block_'.$blockname.'_global_db_replace';
9379          include_once($fullblock.'/lib.php');
9380          if (!function_exists($function)) {
9381              continue;
9382          }
9383  
9384          echo $OUTPUT->notification("Replacing in $blockname blocks...", 'notifysuccess');
9385          $function($search, $replace);
9386          echo $OUTPUT->notification("...finished", 'notifysuccess');
9387      }
9388  
9389      // Trigger an event.
9390      $eventargs = [
9391          'context' => context_system::instance(),
9392          'other' => [
9393              'search' => $search,
9394              'replace' => $replace
9395          ]
9396      ];
9397      $event = \core\event\database_text_field_content_replaced::create($eventargs);
9398      $event->trigger();
9399  
9400      purge_all_caches();
9401  
9402      return true;
9403  }
9404  
9405  /**
9406   * Manage repository settings
9407   *
9408   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
9409   */
9410  class admin_setting_managerepository extends admin_setting {
9411  /** @var string */
9412      private $baseurl;
9413  
9414      /**
9415       * calls parent::__construct with specific arguments
9416       */
9417      public function __construct() {
9418          global $CFG;
9419          parent::__construct('managerepository', get_string('manage', 'repository'), '', '');
9420          $this->baseurl = $CFG->wwwroot . '/' . $CFG->admin . '/repository.php?sesskey=' . sesskey();
9421      }
9422  
9423      /**
9424       * Always returns true, does nothing
9425       *
9426       * @return true
9427       */
9428      public function get_setting() {
9429          return true;
9430      }
9431  
9432      /**
9433       * Always returns true does nothing
9434       *
9435       * @return true
9436       */
9437      public function get_defaultsetting() {
9438          return true;
9439      }
9440  
9441      /**
9442       * Always returns s_managerepository
9443       *
9444       * @return string Always return 's_managerepository'
9445       */
9446      public function get_full_name() {
9447          return 's_managerepository';
9448      }
9449  
9450      /**
9451       * Always returns '' doesn't do anything
9452       */
9453      public function write_setting($data) {
9454          $url = $this->baseurl . '&amp;new=' . $data;
9455          return '';
9456      // TODO
9457      // Should not use redirect and exit here
9458      // Find a better way to do this.
9459      // redirect($url);
9460      // exit;
9461      }
9462  
9463      /**
9464       * Searches repository plugins for one that matches $query
9465       *
9466       * @param string $query The string to search for
9467       * @return bool true if found, false if not
9468       */
9469      public function is_related($query) {
9470          if (parent::is_related($query)) {
9471              return true;
9472          }
9473  
9474          $repositories= core_component::get_plugin_list('repository');
9475          foreach ($repositories as $p => $dir) {
9476              if (strpos($p, $query) !== false) {
9477                  return true;
9478              }
9479          }
9480          foreach (repository::get_types() as $instance) {
9481              $title = $instance->get_typename();
9482              if (strpos(core_text::strtolower($title), $query) !== false) {
9483                  return true;
9484              }
9485          }
9486          return false;
9487      }
9488  
9489      /**
9490       * Helper function that generates a moodle_url object
9491       * relevant to the repository
9492       */
9493  
9494      function repository_action_url($repository) {
9495          return new moodle_url($this->baseurl, array('sesskey'=>sesskey(), 'repos'=>$repository));
9496      }
9497  
9498      /**
9499       * Builds XHTML to display the control
9500       *
9501       * @param string $data Unused
9502       * @param string $query
9503       * @return string XHTML
9504       */
9505      public function output_html($data, $query='') {
9506          global $CFG, $USER, $OUTPUT;
9507  
9508          // Get strings that are used
9509          $strshow = get_string('on', 'repository');
9510          $strhide = get_string('off', 'repository');
9511          $strdelete = get_string('disabled', 'repository');
9512  
9513          $actionchoicesforexisting = array(
9514              'show' => $strshow,
9515              'hide' => $strhide,
9516              'delete' => $strdelete
9517          );
9518  
9519          $actionchoicesfornew = array(
9520              'newon' => $strshow,
9521              'newoff' => $strhide,
9522              'delete' => $strdelete
9523          );
9524  
9525          $return = '';
9526          $return .= $OUTPUT->box_start('generalbox');
9527  
9528          // Set strings that are used multiple times
9529          $settingsstr = get_string('settings');
9530          $disablestr = get_string('disable');
9531  
9532          // Table to list plug-ins
9533          $table = new html_table();
9534          $table->head = array(get_string('name'), get_string('isactive', 'repository'), get_string('order'), $settingsstr);
9535          $table->align = array('left', 'center', 'center', 'center', 'center');
9536          $table->data = array();
9537  
9538          // Get list of used plug-ins
9539          $repositorytypes = repository::get_types();
9540          if (!empty($repositorytypes)) {
9541              // Array to store plugins being used
9542              $alreadyplugins = array();
9543              $totalrepositorytypes = count($repositorytypes);
9544              $updowncount = 1;
9545              foreach ($repositorytypes as $i) {
9546                  $settings = '';
9547                  $typename = $i->get_typename();
9548                  // Display edit link only if you can config the type or if it has multiple instances (e.g. has instance config)
9549                  $typeoptionnames = repository::static_function($typename, 'get_type_option_names');
9550                  $instanceoptionnames = repository::static_function($typename, 'get_instance_option_names');
9551  
9552                  if (!empty($typeoptionnames) || !empty($instanceoptionnames)) {
9553                      // Calculate number of instances in order to display them for the Moodle administrator
9554                      if (!empty($instanceoptionnames)) {
9555                          $params = array();
9556                          $params['context'] = array(context_system::instance());
9557                          $params['onlyvisible'] = false;
9558                          $params['type'] = $typename;
9559                          $admininstancenumber = count(repository::static_function($typename, 'get_instances', $params));
9560                          // site instances
9561                          $admininstancenumbertext = get_string('instancesforsite', 'repository', $admininstancenumber);
9562                          $params['context'] = array();
9563                          $instances = repository::static_function($typename, 'get_instances', $params);
9564                          $courseinstances = array();
9565                          $userinstances = array();
9566  
9567                          foreach ($instances as $instance) {
9568                              $repocontext = context::instance_by_id($instance->instance->contextid);
9569                              if ($repocontext->contextlevel == CONTEXT_COURSE) {
9570                                  $courseinstances[] = $instance;
9571                              } else if ($repocontext->contextlevel == CONTEXT_USER) {
9572                                  $userinstances[] = $instance;
9573                              }
9574                          }
9575                          // course instances
9576                          $instancenumber = count($courseinstances);
9577                          $courseinstancenumbertext = get_string('instancesforcourses', 'repository', $instancenumber);
9578  
9579                          // user private instances
9580                          $instancenumber =  count($userinstances);
9581                          $userinstancenumbertext = get_string('instancesforusers', 'repository', $instancenumber);
9582                      } else {
9583                          $admininstancenumbertext = "";
9584                          $courseinstancenumbertext = "";
9585                          $userinstancenumbertext = "";
9586                      }
9587  
9588                      $settings .= '<a href="' . $this->baseurl . '&amp;action=edit&amp;repos=' . $typename . '">' . $settingsstr .'</a>';
9589  
9590                      $settings .= $OUTPUT->container_start('mdl-left');
9591                      $settings .= '<br/>';
9592                      $settings .= $admininstancenumbertext;
9593                      $settings .= '<br/>';
9594                      $settings .= $courseinstancenumbertext;
9595                      $settings .= '<br/>';
9596                      $settings .= $userinstancenumbertext;
9597                      $settings .= $OUTPUT->container_end();
9598                  }
9599                  // Get the current visibility
9600                  if ($i->get_visible()) {
9601                      $currentaction = 'show';
9602                  } else {
9603                      $currentaction = 'hide';
9604                  }
9605  
9606                  $select = new single_select($this->repository_action_url($typename, 'repos'), 'action', $actionchoicesforexisting, $currentaction, null, 'applyto' . basename($typename));
9607  
9608                  // Display up/down link
9609                  $updown = '';
9610                  // Should be done with CSS instead.
9611                  $spacer = $OUTPUT->spacer(array('height' => 15, 'width' => 15, 'class' => 'smallicon'));
9612  
9613                  if ($updowncount > 1) {
9614                      $updown .= "<a href=\"$this->baseurl&amp;action=moveup&amp;repos=".$typename."\">";
9615                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
9616                  }
9617                  else {
9618                      $updown .= $spacer;
9619                  }
9620                  if ($updowncount < $totalrepositorytypes) {
9621                      $updown .= "<a href=\"$this->baseurl&amp;action=movedown&amp;repos=".$typename."\">";
9622                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
9623                  }
9624                  else {
9625                      $updown .= $spacer;
9626                  }
9627  
9628                  $updowncount++;
9629  
9630                  $table->data[] = array($i->get_readablename(), $OUTPUT->render($select), $updown, $settings);
9631  
9632                  if (!in_array($typename, $alreadyplugins)) {
9633                      $alreadyplugins[] = $typename;
9634                  }
9635              }
9636          }
9637  
9638          // Get all the plugins that exist on disk
9639          $plugins = core_component::get_plugin_list('repository');
9640          if (!empty($plugins)) {
9641              foreach ($plugins as $plugin => $dir) {
9642                  // Check that it has not already been listed
9643                  if (!in_array($plugin, $alreadyplugins)) {
9644                      $select = new single_select($this->repository_action_url($plugin, 'repos'), 'action', $actionchoicesfornew, 'delete', null, 'applyto' . basename($plugin));
9645                      $table->data[] = array(get_string('pluginname', 'repository_'.$plugin), $OUTPUT->render($select), '', '');
9646                  }
9647              }
9648          }
9649  
9650          $return .= html_writer::table($table);
9651          $return .= $OUTPUT->box_end();
9652          return highlight($query, $return);
9653      }
9654  }
9655  
9656  /**
9657   * Special checkbox for enable mobile web service
9658   * If enable then we store the service id of the mobile service into config table
9659   * If disable then we unstore the service id from the config table
9660   */
9661  class admin_setting_enablemobileservice extends admin_setting_configcheckbox {
9662  
9663      /** @var boolean True means that the capability 'webservice/rest:use' is set for authenticated user role */
9664      private $restuse;
9665  
9666      /**
9667       * Return true if Authenticated user role has the capability 'webservice/rest:use', otherwise false.
9668       *
9669       * @return boolean
9670       */
9671      private function is_protocol_cap_allowed() {
9672          global $DB, $CFG;
9673  
9674          // If the $this->restuse variable is not set, it needs to be set.
9675          if (empty($this->restuse) and $this->restuse!==false) {
9676              $params = array();
9677              $params['permission'] = CAP_ALLOW;
9678              $params['roleid'] = $CFG->defaultuserroleid;
9679              $params['capability'] = 'webservice/rest:use';
9680              $this->restuse = $DB->record_exists('role_capabilities', $params);
9681          }
9682  
9683          return $this->restuse;
9684      }
9685  
9686      /**
9687       * Set the 'webservice/rest:use' to the Authenticated user role (allow or not)
9688       * @param type $status true to allow, false to not set
9689       */
9690      private function set_protocol_cap($status) {
9691          global $CFG;
9692          if ($status and !$this->is_protocol_cap_allowed()) {
9693              //need to allow the cap
9694              $permission = CAP_ALLOW;
9695              $assign = true;
9696          } else if (!$status and $this->is_protocol_cap_allowed()){
9697              //need to disallow the cap
9698              $permission = CAP_INHERIT;
9699              $assign = true;
9700          }
9701          if (!empty($assign)) {
9702              $systemcontext = context_system::instance();
9703              assign_capability('webservice/rest:use', $permission, $CFG->defaultuserroleid, $systemcontext->id, true);
9704          }
9705      }
9706  
9707      /**
9708       * Builds XHTML to display the control.
9709       * The main purpose of this overloading is to display a warning when https
9710       * is not supported by the server
9711       * @param string $data Unused
9712       * @param string $query
9713       * @return string XHTML
9714       */
9715      public function output_html($data, $query='') {
9716          global $OUTPUT;
9717          $html = parent::output_html($data, $query);
9718  
9719          if ((string)$data === $this->yes) {
9720              $notifications = tool_mobile\api::get_potential_config_issues(); // Safe to call, plugin available if we reach here.
9721              foreach ($notifications as $notification) {
9722                  $message = get_string($notification[0], $notification[1]);
9723                  $html .= $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING);
9724              }
9725          }
9726  
9727          return $html;
9728      }
9729  
9730      /**
9731       * Retrieves the current setting using the objects name
9732       *
9733       * @return string
9734       */
9735      public function get_setting() {
9736          global $CFG;
9737  
9738          // First check if is not set.
9739          $result = $this->config_read($this->name);
9740          if (is_null($result)) {
9741              return null;
9742          }
9743  
9744          // For install cli script, $CFG->defaultuserroleid is not set so return 0
9745          // Or if web services aren't enabled this can't be,
9746          if (empty($CFG->defaultuserroleid) || empty($CFG->enablewebservices)) {
9747              return 0;
9748          }
9749  
9750          require_once($CFG->dirroot . '/webservice/lib.php');
9751          $webservicemanager = new webservice();
9752          $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9753          if ($mobileservice->enabled and $this->is_protocol_cap_allowed()) {
9754              return $result;
9755          } else {
9756              return 0;
9757          }
9758      }
9759  
9760      /**
9761       * Save the selected setting
9762       *
9763       * @param string $data The selected site
9764       * @return string empty string or error message
9765       */
9766      public function write_setting($data) {
9767          global $DB, $CFG;
9768  
9769          //for install cli script, $CFG->defaultuserroleid is not set so do nothing
9770          if (empty($CFG->defaultuserroleid)) {
9771              return '';
9772          }
9773  
9774          $servicename = MOODLE_OFFICIAL_MOBILE_SERVICE;
9775  
9776          require_once($CFG->dirroot . '/webservice/lib.php');
9777          $webservicemanager = new webservice();
9778  
9779          $updateprotocol = false;
9780          if ((string)$data === $this->yes) {
9781               //code run when enable mobile web service
9782               //enable web service systeme if necessary
9783               set_config('enablewebservices', true);
9784  
9785               //enable mobile service
9786               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9787               $mobileservice->enabled = 1;
9788               $webservicemanager->update_external_service($mobileservice);
9789  
9790               // Enable REST server.
9791               $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
9792  
9793               if (!in_array('rest', $activeprotocols)) {
9794                   $activeprotocols[] = 'rest';
9795                   $updateprotocol = true;
9796               }
9797  
9798               if ($updateprotocol) {
9799                   set_config('webserviceprotocols', implode(',', $activeprotocols));
9800               }
9801  
9802               // Allow rest:use capability for authenticated user.
9803               $this->set_protocol_cap(true);
9804           } else {
9805               // Disable the mobile service.
9806               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9807               $mobileservice->enabled = 0;
9808               $webservicemanager->update_external_service($mobileservice);
9809           }
9810  
9811          return (parent::write_setting($data));
9812      }
9813  }
9814  
9815  /**
9816   * Special class for management of external services
9817   *
9818   * @author Petr Skoda (skodak)
9819   */
9820  class admin_setting_manageexternalservices extends admin_setting {
9821      /**
9822       * Calls parent::__construct with specific arguments
9823       */
9824      public function __construct() {
9825          $this->nosave = true;
9826          parent::__construct('webservicesui', get_string('externalservices', 'webservice'), '', '');
9827      }
9828  
9829      /**
9830       * Always returns true, does nothing
9831       *
9832       * @return true
9833       */
9834      public function get_setting() {
9835          return true;
9836      }
9837  
9838      /**
9839       * Always returns true, does nothing
9840       *
9841       * @return true
9842       */
9843      public function get_defaultsetting() {
9844          return true;
9845      }
9846  
9847      /**
9848       * Always returns '', does not write anything
9849       *
9850       * @return string Always returns ''
9851       */
9852      public function write_setting($data) {
9853      // do not write any setting
9854          return '';
9855      }
9856  
9857      /**
9858       * Checks if $query is one of the available external services
9859       *
9860       * @param string $query The string to search for
9861       * @return bool Returns true if found, false if not
9862       */
9863      public function is_related($query) {
9864          global $DB;
9865  
9866          if (parent::is_related($query)) {
9867              return true;
9868          }
9869  
9870          $services = $DB->get_records('external_services', array(), 'id, name');
9871          foreach ($services as $service) {
9872              if (strpos(core_text::strtolower($service->name), $query) !== false) {
9873                  return true;
9874              }
9875          }
9876          return false;
9877      }
9878  
9879      /**
9880       * Builds the XHTML to display the control
9881       *
9882       * @param string $data Unused
9883       * @param string $query
9884       * @return string
9885       */
9886      public function output_html($data, $query='') {
9887          global $CFG, $OUTPUT, $DB;
9888  
9889          // display strings
9890          $stradministration = get_string('administration');
9891          $stredit = get_string('edit');
9892          $strservice = get_string('externalservice', 'webservice');
9893          $strdelete = get_string('delete');
9894          $strplugin = get_string('plugin', 'admin');
9895          $stradd = get_string('add');
9896          $strfunctions = get_string('functions', 'webservice');
9897          $strusers = get_string('users');
9898          $strserviceusers = get_string('serviceusers', 'webservice');
9899  
9900          $esurl = "$CFG->wwwroot/$CFG->admin/webservice/service.php";
9901          $efurl = "$CFG->wwwroot/$CFG->admin/webservice/service_functions.php";
9902          $euurl = "$CFG->wwwroot/$CFG->admin/webservice/service_users.php";
9903  
9904          // built in services
9905           $services = $DB->get_records_select('external_services', 'component IS NOT NULL', null, 'name');
9906           $return = "";
9907           if (!empty($services)) {
9908              $return .= $OUTPUT->heading(get_string('servicesbuiltin', 'webservice'), 3, 'main');
9909  
9910  
9911  
9912              $table = new html_table();
9913              $table->head  = array($strservice, $strplugin, $strfunctions, $strusers, $stredit);
9914              $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9915              $table->id = 'builtinservices';
9916              $table->attributes['class'] = 'admintable externalservices generaltable';
9917              $table->data  = array();
9918  
9919              // iterate through auth plugins and add to the display table
9920              foreach ($services as $service) {
9921                  $name = $service->name;
9922  
9923                  // hide/show link
9924                  if ($service->enabled) {
9925                      $displayname = "<span>$name</span>";
9926                  } else {
9927                      $displayname = "<span class=\"dimmed_text\">$name</span>";
9928                  }
9929  
9930                  $plugin = $service->component;
9931  
9932                  $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9933  
9934                  if ($service->restrictedusers) {
9935                      $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9936                  } else {
9937                      $users = get_string('allusers', 'webservice');
9938                  }
9939  
9940                  $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9941  
9942                  // add a row to the table
9943                  $table->data[] = array($displayname, $plugin, $functions, $users, $edit);
9944              }
9945              $return .= html_writer::table($table);
9946          }
9947  
9948          // Custom services
9949          $return .= $OUTPUT->heading(get_string('servicescustom', 'webservice'), 3, 'main');
9950          $services = $DB->get_records_select('external_services', 'component IS NULL', null, 'name');
9951  
9952          $table = new html_table();
9953          $table->head  = array($strservice, $strdelete, $strfunctions, $strusers, $stredit);
9954          $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9955          $table->id = 'customservices';
9956          $table->attributes['class'] = 'admintable externalservices generaltable';
9957          $table->data  = array();
9958  
9959          // iterate through auth plugins and add to the display table
9960          foreach ($services as $service) {
9961              $name = $service->name;
9962  
9963              // hide/show link
9964              if ($service->enabled) {
9965                  $displayname = "<span>$name</span>";
9966              } else {
9967                  $displayname = "<span class=\"dimmed_text\">$name</span>";
9968              }
9969  
9970              // delete link
9971              $delete = "<a href=\"$esurl?action=delete&amp;sesskey=".sesskey()."&amp;id=$service->id\">$strdelete</a>";
9972  
9973              $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9974  
9975              if ($service->restrictedusers) {
9976                  $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9977              } else {
9978                  $users = get_string('allusers', 'webservice');
9979              }
9980  
9981              $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9982  
9983              // add a row to the table
9984              $table->data[] = array($displayname, $delete, $functions, $users, $edit);
9985          }
9986          // add new custom service option
9987          $return .= html_writer::table($table);
9988  
9989          $return .= '<br />';
9990          // add a token to the table
9991          $return .= "<a href=\"$esurl?id=0\">$stradd</a>";
9992  
9993          return highlight($query, $return);
9994      }
9995  }
9996  
9997  /**
9998   * Special class for overview of external services
9999   *
10000   * @author Jerome Mouneyrac
10001   */
10002  class admin_setting_webservicesoverview extends admin_setting {
10003  
10004      /**
10005       * Calls parent::__construct with specific arguments
10006       */
10007      public function __construct() {
10008          $this->nosave = true;
10009          parent::__construct('webservicesoverviewui',
10010                          get_string('webservicesoverview', 'webservice'), '', '');
10011      }
10012  
10013      /**
10014       * Always returns true, does nothing
10015       *
10016       * @return true
10017       */
10018      public function get_setting() {
10019          return true;
10020      }
10021  
10022      /**
10023       * Always returns true, does nothing
10024       *
10025       * @return true
10026       */
10027      public function get_defaultsetting() {
10028          return true;
10029      }
10030  
10031      /**
10032       * Always returns '', does not write anything
10033       *
10034       * @return string Always returns ''
10035       */
10036      public function write_setting($data) {
10037          // do not write any setting
10038          return '';
10039      }
10040  
10041      /**
10042       * Builds the XHTML to display the control
10043       *
10044       * @param string $data Unused
10045       * @param string $query
10046       * @return string
10047       */
10048      public function output_html($data, $query='') {
10049          global $CFG, $OUTPUT;
10050  
10051          $return = "";
10052          $brtag = html_writer::empty_tag('br');
10053  
10054          /// One system controlling Moodle with Token
10055          $return .= $OUTPUT->heading(get_string('onesystemcontrolling', 'webservice'), 3, 'main');
10056          $table = new html_table();
10057          $table->head = array(get_string('step', 'webservice'), get_string('status'),
10058              get_string('description'));
10059          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
10060          $table->id = 'onesystemcontrol';
10061          $table->attributes['class'] = 'admintable wsoverview generaltable';
10062          $table->data = array();
10063  
10064          $return .= $brtag . get_string('onesystemcontrollingdescription', 'webservice')
10065                  . $brtag . $brtag;
10066  
10067          /// 1. Enable Web Services
10068          $row = array();
10069          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10070          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10071                          array('href' => $url));
10072          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10073          if ($CFG->enablewebservices) {
10074              $status = get_string('yes');
10075          }
10076          $row[1] = $status;
10077          $row[2] = get_string('enablewsdescription', 'webservice');
10078          $table->data[] = $row;
10079  
10080          /// 2. Enable protocols
10081          $row = array();
10082          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10083          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10084                          array('href' => $url));
10085          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10086          //retrieve activated protocol
10087          $active_protocols = empty($CFG->webserviceprotocols) ?
10088                  array() : explode(',', $CFG->webserviceprotocols);
10089          if (!empty($active_protocols)) {
10090              $status = "";
10091              foreach ($active_protocols as $protocol) {
10092                  $status .= $protocol . $brtag;
10093              }
10094          }
10095          $row[1] = $status;
10096          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10097          $table->data[] = $row;
10098  
10099          /// 3. Create user account
10100          $row = array();
10101          $url = new moodle_url("/user/editadvanced.php?id=-1");
10102          $row[0] = "3. " . html_writer::tag('a', get_string('createuser', 'webservice'),
10103                          array('href' => $url));
10104          $row[1] = "";
10105          $row[2] = get_string('createuserdescription', 'webservice');
10106          $table->data[] = $row;
10107  
10108          /// 4. Add capability to users
10109          $row = array();
10110          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10111          $row[0] = "4. " . html_writer::tag('a', get_string('checkusercapability', 'webservice'),
10112                          array('href' => $url));
10113          $row[1] = "";
10114          $row[2] = get_string('checkusercapabilitydescription', 'webservice');
10115          $table->data[] = $row;
10116  
10117          /// 5. Select a web service
10118          $row = array();
10119          $url = new moodle_url("/admin/settings.php?section=externalservices");
10120          $row[0] = "5. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10121                          array('href' => $url));
10122          $row[1] = "";
10123          $row[2] = get_string('createservicedescription', 'webservice');
10124          $table->data[] = $row;
10125  
10126          /// 6. Add functions
10127          $row = array();
10128          $url = new moodle_url("/admin/settings.php?section=externalservices");
10129          $row[0] = "6. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10130                          array('href' => $url));
10131          $row[1] = "";
10132          $row[2] = get_string('addfunctionsdescription', 'webservice');
10133          $table->data[] = $row;
10134  
10135          /// 7. Add the specific user
10136          $row = array();
10137          $url = new moodle_url("/admin/settings.php?section=externalservices");
10138          $row[0] = "7. " . html_writer::tag('a', get_string('selectspecificuser', 'webservice'),
10139                          array('href' => $url));
10140          $row[1] = "";
10141          $row[2] = get_string('selectspecificuserdescription', 'webservice');
10142          $table->data[] = $row;
10143  
10144          /// 8. Create token for the specific user
10145          $row = array();
10146          $url = new moodle_url('/admin/webservice/tokens.php', ['action' => 'create']);
10147          $row[0] = "8. " . html_writer::tag('a', get_string('createtokenforuser', 'webservice'),
10148                          array('href' => $url));
10149          $row[1] = "";
10150          $row[2] = get_string('createtokenforuserdescription', 'webservice');
10151          $table->data[] = $row;
10152  
10153          /// 9. Enable the documentation
10154          $row = array();
10155          $url = new moodle_url("/admin/search.php?query=enablewsdocumentation");
10156          $row[0] = "9. " . html_writer::tag('a', get_string('enabledocumentation', 'webservice'),
10157                          array('href' => $url));
10158          $status = '<span class="warning">' . get_string('no') . '</span>';
10159          if ($CFG->enablewsdocumentation) {
10160              $status = get_string('yes');
10161          }
10162          $row[1] = $status;
10163          $row[2] = get_string('enabledocumentationdescription', 'webservice');
10164          $table->data[] = $row;
10165  
10166          /// 10. Test the service
10167          $row = array();
10168          $url = new moodle_url("/admin/webservice/testclient.php");
10169          $row[0] = "10. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10170                          array('href' => $url));
10171          $row[1] = "";
10172          $row[2] = get_string('testwithtestclientdescription', 'webservice');
10173          $table->data[] = $row;
10174  
10175          $return .= html_writer::table($table);
10176  
10177          /// Users as clients with token
10178          $return .= $brtag . $brtag . $brtag;
10179          $return .= $OUTPUT->heading(get_string('userasclients', 'webservice'), 3, 'main');
10180          $table = new html_table();
10181          $table->head = array(get_string('step', 'webservice'), get_string('status'),
10182              get_string('description'));
10183          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
10184          $table->id = 'userasclients';
10185          $table->attributes['class'] = 'admintable wsoverview generaltable';
10186          $table->data = array();
10187  
10188          $return .= $brtag . get_string('userasclientsdescription', 'webservice') .
10189                  $brtag . $brtag;
10190  
10191          /// 1. Enable Web Services
10192          $row = array();
10193          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10194          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10195                          array('href' => $url));
10196          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10197          if ($CFG->enablewebservices) {
10198              $status = get_string('yes');
10199          }
10200          $row[1] = $status;
10201          $row[2] = get_string('enablewsdescription', 'webservice');
10202          $table->data[] = $row;
10203  
10204          /// 2. Enable protocols
10205          $row = array();
10206          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10207          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10208                          array('href' => $url));
10209          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10210          //retrieve activated protocol
10211          $active_protocols = empty($CFG->webserviceprotocols) ?
10212                  array() : explode(',', $CFG->webserviceprotocols);
10213          if (!empty($active_protocols)) {
10214              $status = "";
10215              foreach ($active_protocols as $protocol) {
10216                  $status .= $protocol . $brtag;
10217              }
10218          }
10219          $row[1] = $status;
10220          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10221          $table->data[] = $row;
10222  
10223  
10224          /// 3. Select a web service
10225          $row = array();
10226          $url = new moodle_url("/admin/settings.php?section=externalservices");
10227          $row[0] = "3. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10228                          array('href' => $url));
10229          $row[1] = "";
10230          $row[2] = get_string('createserviceforusersdescription', 'webservice');
10231          $table->data[] = $row;
10232  
10233          /// 4. Add functions
10234          $row = array();
10235          $url = new moodle_url("/admin/settings.php?section=externalservices");
10236          $row[0] = "4. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10237                          array('href' => $url));
10238          $row[1] = "";
10239          $row[2] = get_string('addfunctionsdescription', 'webservice');
10240          $table->data[] = $row;
10241  
10242          /// 5. Add capability to users
10243          $row = array();
10244          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10245          $row[0] = "5. " . html_writer::tag('a', get_string('addcapabilitytousers', 'webservice'),
10246                          array('href' => $url));
10247          $row[1] = "";
10248          $row[2] = get_string('addcapabilitytousersdescription', 'webservice');
10249          $table->data[] = $row;
10250  
10251          /// 6. Test the service
10252          $row = array();
10253          $url = new moodle_url("/admin/webservice/testclient.php");
10254          $row[0] = "6. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10255                          array('href' => $url));
10256          $row[1] = "";
10257          $row[2] = get_string('testauserwithtestclientdescription', 'webservice');
10258          $table->data[] = $row;
10259  
10260          $return .= html_writer::table($table);
10261  
10262          return highlight($query, $return);
10263      }
10264  
10265  }
10266  
10267  
10268  /**
10269   * Special class for web service protocol administration.
10270   *
10271   * @author Petr Skoda (skodak)
10272   */
10273  class admin_setting_managewebserviceprotocols extends admin_setting {
10274  
10275      /**
10276       * Calls parent::__construct with specific arguments
10277       */
10278      public function __construct() {
10279          $this->nosave = true;
10280          parent::__construct('webservicesui', get_string('manageprotocols', 'webservice'), '', '');
10281      }
10282  
10283      /**
10284       * Always returns true, does nothing
10285       *
10286       * @return true
10287       */
10288      public function get_setting() {
10289          return true;
10290      }
10291  
10292      /**
10293       * Always returns true, does nothing
10294       *
10295       * @return true
10296       */
10297      public function get_defaultsetting() {
10298          return true;
10299      }
10300  
10301      /**
10302       * Always returns '', does not write anything
10303       *
10304       * @return string Always returns ''
10305       */
10306      public function write_setting($data) {
10307      // do not write any setting
10308          return '';
10309      }
10310  
10311      /**
10312       * Checks if $query is one of the available webservices
10313       *
10314       * @param string $query The string to search for
10315       * @return bool Returns true if found, false if not
10316       */
10317      public function is_related($query) {
10318          if (parent::is_related($query)) {
10319              return true;
10320          }
10321  
10322          $protocols = core_component::get_plugin_list('webservice');
10323          foreach ($protocols as $protocol=>$location) {
10324              if (strpos($protocol, $query) !== false) {
10325                  return true;
10326              }
10327              $protocolstr = get_string('pluginname', 'webservice_'.$protocol);
10328              if (strpos(core_text::strtolower($protocolstr), $query) !== false) {
10329                  return true;
10330              }
10331          }
10332          return false;
10333      }
10334  
10335      /**
10336       * Builds the XHTML to display the control
10337       *
10338       * @param string $data Unused
10339       * @param string $query
10340       * @return string
10341       */
10342      public function output_html($data, $query='') {
10343          global $CFG, $OUTPUT;
10344  
10345          // display strings
10346          $stradministration = get_string('administration');
10347          $strsettings = get_string('settings');
10348          $stredit = get_string('edit');
10349          $strprotocol = get_string('protocol', 'webservice');
10350          $strenable = get_string('enable');
10351          $strdisable = get_string('disable');
10352          $strversion = get_string('version');
10353  
10354          $protocols_available = core_component::get_plugin_list('webservice');
10355          $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
10356          ksort($protocols_available);
10357  
10358          foreach ($activeprotocols as $key => $protocol) {
10359              if (empty($protocols_available[$protocol])) {
10360                  unset($activeprotocols[$key]);
10361              }
10362          }
10363  
10364          $return = $OUTPUT->heading(get_string('actwebserviceshhdr', 'webservice'), 3, 'main');
10365          if (in_array('xmlrpc', $activeprotocols)) {
10366              $notify = new \core\output\notification(get_string('xmlrpcwebserviceenabled', 'admin'),
10367                  \core\output\notification::NOTIFY_WARNING);
10368              $return .= $OUTPUT->render($notify);
10369          }
10370          $return .= $OUTPUT->box_start('generalbox webservicesui');
10371  
10372          $table = new html_table();
10373          $table->head  = array($strprotocol, $strversion, $strenable, $strsettings);
10374          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
10375          $table->id = 'webserviceprotocols';
10376          $table->attributes['class'] = 'admintable generaltable';
10377          $table->data  = array();
10378  
10379          // iterate through auth plugins and add to the display table
10380          $url = "$CFG->wwwroot/$CFG->admin/webservice/protocols.php?sesskey=" . sesskey();
10381          foreach ($protocols_available as $protocol => $location) {
10382              $name = get_string('pluginname', 'webservice_'.$protocol);
10383  
10384              $plugin = new stdClass();
10385              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/version.php')) {
10386                  include($CFG->dirroot.'/webservice/'.$protocol.'/version.php');
10387              }
10388              $version = isset($plugin->version) ? $plugin->version : '';
10389  
10390              // hide/show link
10391              if (in_array($protocol, $activeprotocols)) {
10392                  $hideshow = "<a href=\"$url&amp;action=disable&amp;webservice=$protocol\">";
10393                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
10394                  $displayname = "<span>$name</span>";
10395              } else {
10396                  $hideshow = "<a href=\"$url&amp;action=enable&amp;webservice=$protocol\">";
10397                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
10398                  $displayname = "<span class=\"dimmed_text\">$name</span>";
10399              }
10400  
10401              // settings link
10402              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/settings.php')) {
10403                  $settings = "<a href=\"settings.php?section=webservicesetting$protocol\">$strsettings</a>";
10404              } else {
10405                  $settings = '';
10406              }
10407  
10408              // add a row to the table
10409              $table->data[] = array($displayname, $version, $hideshow, $settings);
10410          }
10411          $return .= html_writer::table($table);
10412          $return .= get_string('configwebserviceplugins', 'webservice');
10413          $return .= $OUTPUT->box_end();
10414  
10415          return highlight($query, $return);
10416      }
10417  }
10418  
10419  /**
10420   * Colour picker
10421   *
10422   * @copyright 2010 Sam Hemelryk
10423   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10424   */
10425  class admin_setting_configcolourpicker extends admin_setting {
10426  
10427      /**
10428       * Information for previewing the colour
10429       *
10430       * @var array|null
10431       */
10432      protected $previewconfig = null;
10433  
10434      /**
10435       * Use default when empty.
10436       */
10437      protected $usedefaultwhenempty = true;
10438  
10439      /**
10440       *
10441       * @param string $name
10442       * @param string $visiblename
10443       * @param string $description
10444       * @param string $defaultsetting
10445       * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor');
10446       */
10447      public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig = null,
10448              $usedefaultwhenempty = true) {
10449          $this->previewconfig = $previewconfig;
10450          $this->usedefaultwhenempty = $usedefaultwhenempty;
10451          parent::__construct($name, $visiblename, $description, $defaultsetting);
10452          $this->set_force_ltr(true);
10453      }
10454  
10455      /**
10456       * Return the setting
10457       *
10458       * @return mixed returns config if successful else null
10459       */
10460      public function get_setting() {
10461          return $this->config_read($this->name);
10462      }
10463  
10464      /**
10465       * Saves the setting
10466       *
10467       * @param string $data
10468       * @return bool
10469       */
10470      public function write_setting($data) {
10471          $data = $this->validate($data);
10472          if ($data === false) {
10473              return  get_string('validateerror', 'admin');
10474          }
10475          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
10476      }
10477  
10478      /**
10479       * Validates the colour that was entered by the user
10480       *
10481       * @param string $data
10482       * @return string|false
10483       */
10484      protected function validate($data) {
10485          /**
10486           * List of valid HTML colour names
10487           *
10488           * @var array
10489           */
10490           $colornames = array(
10491              'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure',
10492              'beige', 'bisque', 'black', 'blanchedalmond', 'blue',
10493              'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse',
10494              'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson',
10495              'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray',
10496              'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta',
10497              'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
10498              'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray',
10499              'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
10500              'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick',
10501              'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro',
10502              'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green',
10503              'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo',
10504              'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen',
10505              'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',
10506              'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen',
10507              'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue',
10508              'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow',
10509              'lime', 'limegreen', 'linen', 'magenta', 'maroon',
10510              'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',
10511              'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
10512              'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',
10513              'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',
10514              'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',
10515              'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',
10516              'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',
10517              'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown',
10518              'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',
10519              'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan',
10520              'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white',
10521              'whitesmoke', 'yellow', 'yellowgreen'
10522          );
10523  
10524          if (preg_match('/^#?([[:xdigit:]]{3}){1,2}$/', $data)) {
10525              if (strpos($data, '#')!==0) {
10526                  $data = '#'.$data;
10527              }
10528              return $data;
10529          } else if (in_array(strtolower($data), $colornames)) {
10530              return $data;
10531          } else if (preg_match('/rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)/i', $data)) {
10532              return $data;
10533          } else if (preg_match('/rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)/i', $data)) {
10534              return $data;
10535          } else if (preg_match('/hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)/i', $data)) {
10536              return $data;
10537          } else if (preg_match('/hsla\(\d{0,3}\, ?\d{0,3}%,\d{0,3}%\, ?\d(\.\d)?\)/i', $data)) {
10538              return $data;
10539          } else if (($data == 'transparent') || ($data == 'currentColor') || ($data == 'inherit')) {
10540              return $data;
10541          } else if (empty($data)) {
10542              if ($this->usedefaultwhenempty){
10543                  return $this->defaultsetting;
10544              } else {
10545                  return '';
10546              }
10547          } else {
10548              return false;
10549          }
10550      }
10551  
10552      /**
10553       * Generates the HTML for the setting
10554       *
10555       * @global moodle_page $PAGE
10556       * @global core_renderer $OUTPUT
10557       * @param string $data
10558       * @param string $query
10559       */
10560      public function output_html($data, $query = '') {
10561          global $PAGE, $OUTPUT;
10562  
10563          $icon = new pix_icon('i/loading', get_string('loading', 'admin'), 'moodle', ['class' => 'loadingicon']);
10564          $context = (object) [
10565              'id' => $this->get_id(),
10566              'name' => $this->get_full_name(),
10567              'value' => $data,
10568              'icon' => $icon->export_for_template($OUTPUT),
10569              'haspreviewconfig' => !empty($this->previewconfig),
10570              'forceltr' => $this->get_force_ltr(),
10571              'readonly' => $this->is_readonly(),
10572          ];
10573  
10574          $element = $OUTPUT->render_from_template('core_admin/setting_configcolourpicker', $context);
10575          $PAGE->requires->js_init_call('M.util.init_colour_picker', array($this->get_id(), $this->previewconfig));
10576  
10577          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '',
10578              $this->get_defaultsetting(), $query);
10579      }
10580  
10581  }
10582  
10583  
10584  /**
10585   * Class used for uploading of one file into file storage,
10586   * the file name is stored in config table.
10587   *
10588   * Please note you need to implement your own '_pluginfile' callback function,
10589   * this setting only stores the file, it does not deal with file serving.
10590   *
10591   * @copyright 2013 Petr Skoda {@link http://skodak.org}
10592   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10593   */
10594  class admin_setting_configstoredfile extends admin_setting {
10595      /** @var array file area options - should be one file only */
10596      protected $options;
10597      /** @var string name of the file area */
10598      protected $filearea;
10599      /** @var int intemid */
10600      protected $itemid;
10601      /** @var string used for detection of changes */
10602      protected $oldhashes;
10603  
10604      /**
10605       * Create new stored file setting.
10606       *
10607       * @param string $name low level setting name
10608       * @param string $visiblename human readable setting name
10609       * @param string $description description of setting
10610       * @param mixed $filearea file area for file storage
10611       * @param int $itemid itemid for file storage
10612       * @param array $options file area options
10613       */
10614      public function __construct($name, $visiblename, $description, $filearea, $itemid = 0, array $options = null) {
10615          parent::__construct($name, $visiblename, $description, '');
10616          $this->filearea = $filearea;
10617          $this->itemid   = $itemid;
10618          $this->options  = (array)$options;
10619          $this->customcontrol = true;
10620      }
10621  
10622      /**
10623       * Applies defaults and returns all options.
10624       * @return array
10625       */
10626      protected function get_options() {
10627          global $CFG;
10628  
10629          require_once("$CFG->libdir/filelib.php");
10630          require_once("$CFG->dirroot/repository/lib.php");
10631          $defaults = array(
10632              'mainfile' => '', 'subdirs' => 0, 'maxbytes' => -1, 'maxfiles' => 1,
10633              'accepted_types' => '*', 'return_types' => FILE_INTERNAL, 'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED,
10634              'context' => context_system::instance());
10635          foreach($this->options as $k => $v) {
10636              $defaults[$k] = $v;
10637          }
10638  
10639          return $defaults;
10640      }
10641  
10642      public function get_setting() {
10643          return $this->config_read($this->name);
10644      }
10645  
10646      public function write_setting($data) {
10647          global $USER;
10648  
10649          // Let's not deal with validation here, this is for admins only.
10650          $current = $this->get_setting();
10651          if (empty($data) && $current === null) {
10652              // This will be the case when applying default settings (installation).
10653              return ($this->config_write($this->name, '') ? '' : get_string('errorsetting', 'admin'));
10654          } else if (!is_number($data)) {
10655              // Draft item id is expected here!
10656              return get_string('errorsetting', 'admin');
10657          }
10658  
10659          $options = $this->get_options();
10660          $fs = get_file_storage();
10661          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10662  
10663          $this->oldhashes = null;
10664          if ($current) {
10665              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10666              if ($file = $fs->get_file_by_hash($hash)) {
10667                  $this->oldhashes = $file->get_contenthash().$file->get_pathnamehash();
10668              }
10669              unset($file);
10670          }
10671  
10672          if ($fs->file_exists($options['context']->id, $component, $this->filearea, $this->itemid, '/', '.')) {
10673              // Make sure the settings form was not open for more than 4 days and draft areas deleted in the meantime.
10674              // But we can safely ignore that if the destination area is empty, so that the user is not prompt
10675              // with an error because the draft area does not exist, as he did not use it.
10676              $usercontext = context_user::instance($USER->id);
10677              if (!$fs->file_exists($usercontext->id, 'user', 'draft', $data, '/', '.') && $current !== '') {
10678                  return get_string('errorsetting', 'admin');
10679              }
10680          }
10681  
10682          file_save_draft_area_files($data, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10683          $files = $fs->get_area_files($options['context']->id, $component, $this->filearea, $this->itemid, 'sortorder,filepath,filename', false);
10684  
10685          $filepath = '';
10686          if ($files) {
10687              /** @var stored_file $file */
10688              $file = reset($files);
10689              $filepath = $file->get_filepath().$file->get_filename();
10690          }
10691  
10692          return ($this->config_write($this->name, $filepath) ? '' : get_string('errorsetting', 'admin'));
10693      }
10694  
10695      public function post_write_settings($original) {
10696          $options = $this->get_options();
10697          $fs = get_file_storage();
10698          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10699  
10700          $current = $this->get_setting();
10701          $newhashes = null;
10702          if ($current) {
10703              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10704              if ($file = $fs->get_file_by_hash($hash)) {
10705                  $newhashes = $file->get_contenthash().$file->get_pathnamehash();
10706              }
10707              unset($file);
10708          }
10709  
10710          if ($this->oldhashes === $newhashes) {
10711              $this->oldhashes = null;
10712              return false;
10713          }
10714          $this->oldhashes = null;
10715  
10716          $callbackfunction = $this->updatedcallback;
10717          if (!empty($callbackfunction) and function_exists($callbackfunction)) {
10718              $callbackfunction($this->get_full_name());
10719          }
10720          return true;
10721      }
10722  
10723      public function output_html($data, $query = '') {
10724          global $CFG;
10725  
10726          $options = $this->get_options();
10727          $id = $this->get_id();
10728          $elname = $this->get_full_name();
10729          $draftitemid = file_get_submitted_draft_itemid($elname);
10730          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10731          file_prepare_draft_area($draftitemid, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10732  
10733          // Filemanager form element implementation is far from optimal, we need to rework this if we ever fix it...
10734          require_once("$CFG->dirroot/lib/form/filemanager.php");
10735  
10736          $fmoptions = new stdClass();
10737          $fmoptions->mainfile       = $options['mainfile'];
10738          $fmoptions->maxbytes       = $options['maxbytes'];
10739          $fmoptions->maxfiles       = $options['maxfiles'];
10740          $fmoptions->subdirs        = $options['subdirs'];
10741          $fmoptions->accepted_types = $options['accepted_types'];
10742          $fmoptions->return_types   = $options['return_types'];
10743          $fmoptions->context        = $options['context'];
10744          $fmoptions->areamaxbytes   = $options['areamaxbytes'];
10745  
10746          $fm = new MoodleQuickForm_filemanager($elname, $this->visiblename, ['id' => $id], $fmoptions);
10747          $fm->setValue($draftitemid);
10748  
10749          return format_admin_setting($this, $this->visiblename,
10750              '<div class="form-filemanager" data-fieldtype="filemanager">' . $fm->toHtml() . '</div>',
10751              $this->description, true, '', '', $query);
10752      }
10753  }
10754  
10755  
10756  /**
10757   * Administration interface for user specified regular expressions for device detection.
10758   *
10759   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10760   */
10761  class admin_setting_devicedetectregex extends admin_setting {
10762  
10763      /**
10764       * Calls parent::__construct with specific args
10765       *
10766       * @param string $name
10767       * @param string $visiblename
10768       * @param string $description
10769       * @param mixed $defaultsetting
10770       */
10771      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
10772          global $CFG;
10773          parent::__construct($name, $visiblename, $description, $defaultsetting);
10774      }
10775  
10776      /**
10777       * Return the current setting(s)
10778       *
10779       * @return array Current settings array
10780       */
10781      public function get_setting() {
10782          global $CFG;
10783  
10784          $config = $this->config_read($this->name);
10785          if (is_null($config)) {
10786              return null;
10787          }
10788  
10789          return $this->prepare_form_data($config);
10790      }
10791  
10792      /**
10793       * Save selected settings
10794       *
10795       * @param array $data Array of settings to save
10796       * @return bool
10797       */
10798      public function write_setting($data) {
10799          if (empty($data)) {
10800              $data = array();
10801          }
10802  
10803          if ($this->config_write($this->name, $this->process_form_data($data))) {
10804              return ''; // success
10805          } else {
10806              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
10807          }
10808      }
10809  
10810      /**
10811       * Return XHTML field(s) for regexes
10812       *
10813       * @param array $data Array of options to set in HTML
10814       * @return string XHTML string for the fields and wrapping div(s)
10815       */
10816      public function output_html($data, $query='') {
10817          global $OUTPUT;
10818  
10819          $context = (object) [
10820              'expressions' => [],
10821              'name' => $this->get_full_name()
10822          ];
10823  
10824          if (empty($data)) {
10825              $looplimit = 1;
10826          } else {
10827              $looplimit = (count($data)/2)+1;
10828          }
10829  
10830          for ($i=0; $i<$looplimit; $i++) {
10831  
10832              $expressionname = 'expression'.$i;
10833  
10834              if (!empty($data[$expressionname])){
10835                  $expression = $data[$expressionname];
10836              } else {
10837                  $expression = '';
10838              }
10839  
10840              $valuename = 'value'.$i;
10841  
10842              if (!empty($data[$valuename])){
10843                  $value = $data[$valuename];
10844              } else {
10845                  $value= '';
10846              }
10847  
10848              $context->expressions[] = [
10849                  'index' => $i,
10850                  'expression' => $expression,
10851                  'value' => $value
10852              ];
10853          }
10854  
10855          $element = $OUTPUT->render_from_template('core_admin/setting_devicedetectregex', $context);
10856  
10857          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
10858      }
10859  
10860      /**
10861       * Converts the string of regexes
10862       *
10863       * @see self::process_form_data()
10864       * @param $regexes string of regexes
10865       * @return array of form fields and their values
10866       */
10867      protected function prepare_form_data($regexes) {
10868  
10869          $regexes = json_decode($regexes);
10870  
10871          $form = array();
10872  
10873          $i = 0;
10874  
10875          foreach ($regexes as $value => $regex) {
10876              $expressionname  = 'expression'.$i;
10877              $valuename = 'value'.$i;
10878  
10879              $form[$expressionname] = $regex;
10880              $form[$valuename] = $value;
10881              $i++;
10882          }
10883  
10884          return $form;
10885      }
10886  
10887      /**
10888       * Converts the data from admin settings form into a string of regexes
10889       *
10890       * @see self::prepare_form_data()
10891       * @param array $data array of admin form fields and values
10892       * @return false|string of regexes
10893       */
10894      protected function process_form_data(array $form) {
10895  
10896          $count = count($form); // number of form field values
10897  
10898          if ($count % 2) {
10899              // we must get five fields per expression
10900              return false;
10901          }
10902  
10903          $regexes = array();
10904          for ($i = 0; $i < $count / 2; $i++) {
10905              $expressionname  = "expression".$i;
10906              $valuename       = "value".$i;
10907  
10908              $expression = trim($form['expression'.$i]);
10909              $value      = trim($form['value'.$i]);
10910  
10911              if (empty($expression)){
10912                  continue;
10913              }
10914  
10915              $regexes[$value] = $expression;
10916          }
10917  
10918          $regexes = json_encode($regexes);
10919  
10920          return $regexes;
10921      }
10922  
10923  }
10924  
10925  /**
10926   * Multiselect for current modules
10927   *
10928   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10929   */
10930  class admin_setting_configmultiselect_modules extends admin_setting_configmultiselect {
10931      private $excludesystem;
10932  
10933      /**
10934       * Calls parent::__construct - note array $choices is not required
10935       *
10936       * @param string $name setting name
10937       * @param string $visiblename localised setting name
10938       * @param string $description setting description
10939       * @param array $defaultsetting a plain array of default module ids
10940       * @param bool $excludesystem If true, excludes modules with 'system' archetype
10941       */
10942      public function __construct($name, $visiblename, $description, $defaultsetting = array(),
10943              $excludesystem = true) {
10944          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
10945          $this->excludesystem = $excludesystem;
10946      }
10947  
10948      /**
10949       * Loads an array of current module choices
10950       *
10951       * @return bool always return true
10952       */
10953      public function load_choices() {
10954          if (is_array($this->choices)) {
10955              return true;
10956          }
10957          $this->choices = array();
10958  
10959          global $CFG, $DB;
10960          $records = $DB->get_records('modules', array('visible'=>1), 'name');
10961          foreach ($records as $record) {
10962              // Exclude modules if the code doesn't exist
10963              if (file_exists("$CFG->dirroot/mod/$record->name/lib.php")) {
10964                  // Also exclude system modules (if specified)
10965                  if (!($this->excludesystem &&
10966                          plugin_supports('mod', $record->name, FEATURE_MOD_ARCHETYPE) ===
10967                          MOD_ARCHETYPE_SYSTEM)) {
10968                      $this->choices[$record->id] = $record->name;
10969                  }
10970              }
10971          }
10972          return true;
10973      }
10974  }
10975  
10976  /**
10977   * Admin setting to show if a php extension is enabled or not.
10978   *
10979   * @copyright 2013 Damyon Wiese
10980   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10981   */
10982  class admin_setting_php_extension_enabled extends admin_setting {
10983  
10984      /** @var string The name of the extension to check for */
10985      private $extension;
10986  
10987      /**
10988       * Calls parent::__construct with specific arguments
10989       */
10990      public function __construct($name, $visiblename, $description, $extension) {
10991          $this->extension = $extension;
10992          $this->nosave = true;
10993          parent::__construct($name, $visiblename, $description, '');
10994      }
10995  
10996      /**
10997       * Always returns true, does nothing
10998       *
10999       * @return true
11000       */
11001      public function get_setting() {
11002          return true;
11003      }
11004  
11005      /**
11006       * Always returns true, does nothing
11007       *
11008       * @return true
11009       */
11010      public function get_defaultsetting() {
11011          return true;
11012      }
11013  
11014      /**
11015       * Always returns '', does not write anything
11016       *
11017       * @return string Always returns ''
11018       */
11019      public function write_setting($data) {
11020          // Do not write any setting.
11021          return '';
11022      }
11023  
11024      /**
11025       * Outputs the html for this setting.
11026       * @return string Returns an XHTML string
11027       */
11028      public function output_html($data, $query='') {
11029          global $OUTPUT;
11030  
11031          $o = '';
11032          if (!extension_loaded($this->extension)) {
11033              $warning = $OUTPUT->pix_icon('i/warning', '', '', array('role' => 'presentation')) . ' ' . $this->description;
11034  
11035              $o .= format_admin_setting($this, $this->visiblename, $warning);
11036          }
11037          return $o;
11038      }
11039  }
11040  
11041  /**
11042   * Server timezone setting.
11043   *
11044   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11045   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11046   * @author    Petr Skoda <petr.skoda@totaralms.com>
11047   */
11048  class admin_setting_servertimezone extends admin_setting_configselect {
11049      /**
11050       * Constructor.
11051       */
11052      public function __construct() {
11053          $default = core_date::get_default_php_timezone();
11054          if ($default === 'UTC') {
11055              // Nobody really wants UTC, so instead default selection to the country that is confused by the UTC the most.
11056              $default = 'Europe/London';
11057          }
11058  
11059          parent::__construct('timezone',
11060              new lang_string('timezone', 'core_admin'),
11061              new lang_string('configtimezone', 'core_admin'), $default, null);
11062      }
11063  
11064      /**
11065       * Lazy load timezone options.
11066       * @return bool true if loaded, false if error
11067       */
11068      public function load_choices() {
11069          global $CFG;
11070          if (is_array($this->choices)) {
11071              return true;
11072          }
11073  
11074          $current = isset($CFG->timezone) ? $CFG->timezone : null;
11075          $this->choices = core_date::get_list_of_timezones($current, false);
11076          if ($current == 99) {
11077              // Do not show 99 unless it is current value, we want to get rid of it over time.
11078              $this->choices['99'] = new lang_string('timezonephpdefault', 'core_admin',
11079                  core_date::get_default_php_timezone());
11080          }
11081  
11082          return true;
11083      }
11084  }
11085  
11086  /**
11087   * Forced user timezone setting.
11088   *
11089   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11090   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11091   * @author    Petr Skoda <petr.skoda@totaralms.com>
11092   */
11093  class admin_setting_forcetimezone extends admin_setting_configselect {
11094      /**
11095       * Constructor.
11096       */
11097      public function __construct() {
11098          parent::__construct('forcetimezone',
11099              new lang_string('forcetimezone', 'core_admin'),
11100              new lang_string('helpforcetimezone', 'core_admin'), '99', null);
11101      }
11102  
11103      /**
11104       * Lazy load timezone options.
11105       * @return bool true if loaded, false if error
11106       */
11107      public function load_choices() {
11108          global $CFG;
11109          if (is_array($this->choices)) {
11110              return true;
11111          }
11112  
11113          $current = isset($CFG->forcetimezone) ? $CFG->forcetimezone : null;
11114          $this->choices = core_date::get_list_of_timezones($current, true);
11115          $this->choices['99'] = new lang_string('timezonenotforced', 'core_admin');
11116  
11117          return true;
11118      }
11119  }
11120  
11121  
11122  /**
11123   * Search setup steps info.
11124   *
11125   * @package core
11126   * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
11127   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11128   */
11129  class admin_setting_searchsetupinfo extends admin_setting {
11130  
11131      /**
11132       * Calls parent::__construct with specific arguments
11133       */
11134      public function __construct() {
11135          $this->nosave = true;
11136          parent::__construct('searchsetupinfo', '', '', '');
11137      }
11138  
11139      /**
11140       * Always returns true, does nothing
11141       *
11142       * @return true
11143       */
11144      public function get_setting() {
11145          return true;
11146      }
11147  
11148      /**
11149       * Always returns true, does nothing
11150       *
11151       * @return true
11152       */
11153      public function get_defaultsetting() {
11154          return true;
11155      }
11156  
11157      /**
11158       * Always returns '', does not write anything
11159       *
11160       * @param array $data
11161       * @return string Always returns ''
11162       */
11163      public function write_setting($data) {
11164          // Do not write any setting.
11165          return '';
11166      }
11167  
11168      /**
11169       * Builds the HTML to display the control
11170       *
11171       * @param string $data Unused
11172       * @param string $query
11173       * @return string
11174       */
11175      public function output_html($data, $query='') {
11176          global $CFG, $OUTPUT, $ADMIN;
11177  
11178          $return = '';
11179          $brtag = html_writer::empty_tag('br');
11180  
11181          $searchareas = \core_search\manager::get_search_areas_list();
11182          $anyenabled = !empty(\core_search\manager::get_search_areas_list(true));
11183          $anyindexed = false;
11184          foreach ($searchareas as $areaid => $searcharea) {
11185              list($componentname, $varname) = $searcharea->get_config_var_name();
11186              if (get_config($componentname, $varname . '_indexingstart')) {
11187                  $anyindexed = true;
11188                  break;
11189              }
11190          }
11191  
11192          $return .= $OUTPUT->heading(get_string('searchsetupinfo', 'admin'), 3, 'main');
11193  
11194          $table = new html_table();
11195          $table->head = array(get_string('step', 'search'), get_string('status'));
11196          $table->colclasses = array('leftalign step', 'leftalign status');
11197          $table->id = 'searchsetup';
11198          $table->attributes['class'] = 'admintable generaltable';
11199          $table->data = array();
11200  
11201          $return .= $brtag . get_string('searchsetupdescription', 'search') . $brtag . $brtag;
11202  
11203          // Select a search engine.
11204          $row = array();
11205          $url = new moodle_url('/admin/settings.php?section=manageglobalsearch#admin-searchengine');
11206          $row[0] = '1. ' . html_writer::tag('a', get_string('selectsearchengine', 'admin'),
11207                          array('href' => $url));
11208  
11209          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11210          if (!empty($CFG->searchengine)) {
11211              $status = html_writer::tag('span', get_string('pluginname', 'search_' . $CFG->searchengine),
11212                  array('class' => 'badge badge-success'));
11213  
11214          }
11215          $row[1] = $status;
11216          $table->data[] = $row;
11217  
11218          // Available areas.
11219          $row = array();
11220          $url = new moodle_url('/admin/searchareas.php');
11221          $row[0] = '2. ' . html_writer::tag('a', get_string('enablesearchareas', 'admin'),
11222                          array('href' => $url));
11223  
11224          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11225          if ($anyenabled) {
11226              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11227  
11228          }
11229          $row[1] = $status;
11230          $table->data[] = $row;
11231  
11232          // Setup search engine.
11233          $row = array();
11234          if (empty($CFG->searchengine)) {
11235              $row[0] = '3. ' . get_string('setupsearchengine', 'admin');
11236              $row[1] = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11237          } else {
11238              if ($ADMIN->locate('search' . $CFG->searchengine)) {
11239                  $url = new moodle_url('/admin/settings.php?section=search' . $CFG->searchengine);
11240                  $row[0] = '3. ' . html_writer::link($url, get_string('setupsearchengine', 'core_admin'));
11241              } else {
11242                  $row[0] = '3. ' . get_string('setupsearchengine', 'core_admin');
11243              }
11244  
11245              // Check the engine status.
11246              $searchengine = \core_search\manager::search_engine_instance();
11247              try {
11248                  $serverstatus = $searchengine->is_server_ready();
11249              } catch (\moodle_exception $e) {
11250                  $serverstatus = $e->getMessage();
11251              }
11252              if ($serverstatus === true) {
11253                  $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11254              } else {
11255                  $status = html_writer::tag('span', $serverstatus, array('class' => 'badge badge-danger'));
11256              }
11257              $row[1] = $status;
11258          }
11259          $table->data[] = $row;
11260  
11261          // Indexed data.
11262          $row = array();
11263          $url = new moodle_url('/admin/searchareas.php');
11264          $row[0] = '4. ' . html_writer::tag('a', get_string('indexdata', 'admin'), array('href' => $url));
11265          if ($anyindexed) {
11266              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11267          } else {
11268              $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11269          }
11270          $row[1] = $status;
11271          $table->data[] = $row;
11272  
11273          // Enable global search.
11274          $row = array();
11275          $url = new moodle_url("/admin/search.php?query=enableglobalsearch");
11276          $row[0] = '5. ' . html_writer::tag('a', get_string('enableglobalsearch', 'admin'),
11277                          array('href' => $url));
11278          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11279          if (\core_search\manager::is_global_search_enabled()) {
11280              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11281          }
11282          $row[1] = $status;
11283          $table->data[] = $row;
11284  
11285          // Replace front page search.
11286          $row = array();
11287          $url = new moodle_url("/admin/search.php?query=searchincludeallcourses");
11288          $row[0] = '6. ' . html_writer::tag('a', get_string('replacefrontsearch', 'admin'),
11289                                             array('href' => $url));
11290          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11291          if (\core_search\manager::can_replace_course_search()) {
11292              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11293          }
11294          $row[1] = $status;
11295          $table->data[] = $row;
11296  
11297          $return .= html_writer::table($table);
11298  
11299          return highlight($query, $return);
11300      }
11301  
11302  }
11303  
11304  /**
11305   * Used to validate the contents of SCSS code and ensuring they are parsable.
11306   *
11307   * It does not attempt to detect undefined SCSS variables because it is designed
11308   * to be used without knowledge of other config/scss included.
11309   *
11310   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11311   * @copyright 2016 Dan Poltawski <dan@moodle.com>
11312   */
11313  class admin_setting_scsscode extends admin_setting_configtextarea {
11314  
11315      /**
11316       * Validate the contents of the SCSS to ensure its parsable. Does not
11317       * attempt to detect undefined scss variables.
11318       *
11319       * @param string $data The scss code from text field.
11320       * @return mixed bool true for success or string:error on failure.
11321       */
11322      public function validate($data) {
11323          if (empty($data)) {
11324              return true;
11325          }
11326  
11327          $scss = new core_scss();
11328          try {
11329              $scss->compile($data);
11330          } catch (ScssPhp\ScssPhp\Exception\ParserException $e) {
11331              return get_string('scssinvalid', 'admin', $e->getMessage());
11332          } catch (ScssPhp\ScssPhp\Exception\CompilerException $e) {
11333              // Silently ignore this - it could be a scss variable defined from somewhere
11334              // else which we are not examining here.
11335              return true;
11336          }
11337  
11338          return true;
11339      }
11340  }
11341  
11342  
11343  /**
11344   * Administration setting to define a list of file types.
11345   *
11346   * @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
11347   * @copyright 2017 David Mudrák <david@moodle.com>
11348   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11349   */
11350  class admin_setting_filetypes extends admin_setting_configtext {
11351  
11352      /** @var array Allow selection from these file types only. */
11353      protected $onlytypes = [];
11354  
11355      /** @var bool Allow selection of 'All file types' (will be stored as '*'). */
11356      protected $allowall = true;
11357  
11358      /** @var core_form\filetypes_util instance to use as a helper. */
11359      protected $util = null;
11360  
11361      /**
11362       * Constructor.
11363       *
11364       * @param string $name Unique ascii name like 'mycoresetting' or 'myplugin/mysetting'
11365       * @param string $visiblename Localised label of the setting
11366       * @param string $description Localised description of the setting
11367       * @param string $defaultsetting Default setting value.
11368       * @param array $options Setting widget options, an array with optional keys:
11369       *   'onlytypes' => array Allow selection from these file types only; for example ['onlytypes' => ['web_image']].
11370       *   'allowall' => bool Allow to select 'All file types', defaults to true. Does not apply if onlytypes are set.
11371       */
11372      public function __construct($name, $visiblename, $description, $defaultsetting = '', array $options = []) {
11373  
11374          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW);
11375  
11376          if (array_key_exists('onlytypes', $options) && is_array($options['onlytypes'])) {
11377              $this->onlytypes = $options['onlytypes'];
11378          }
11379  
11380          if (!$this->onlytypes && array_key_exists('allowall', $options)) {
11381              $this->allowall = (bool)$options['allowall'];
11382          }
11383  
11384          $this->util = new \core_form\filetypes_util();
11385      }
11386  
11387      /**
11388       * Normalize the user's input and write it to the database as comma separated list.
11389       *
11390       * Comma separated list as a text representation of the array was chosen to
11391       * make this compatible with how the $CFG->courseoverviewfilesext values are stored.
11392       *
11393       * @param string $data Value submitted by the admin.
11394       * @return string Epty string if all good, error message otherwise.
11395       */
11396      public function write_setting($data) {
11397          return parent::write_setting(implode(',', $this->util->normalize_file_types($data)));
11398      }
11399  
11400      /**
11401       * Validate data before storage
11402       *
11403       * @param string $data The setting values provided by the admin
11404       * @return bool|string True if ok, the string if error found
11405       */
11406      public function validate($data) {
11407          $parentcheck = parent::validate($data);
11408          if ($parentcheck !== true) {
11409              return $parentcheck;
11410          }
11411  
11412          // Check for unknown file types.
11413          if ($unknown = $this->util->get_unknown_file_types($data)) {
11414              return get_string('filetypesunknown', 'core_form', implode(', ', $unknown));
11415          }
11416  
11417          // Check for disallowed file types.
11418          if ($notlisted = $this->util->get_not_listed($data, $this->onlytypes)) {
11419              return get_string('filetypesnotallowed', 'core_form', implode(', ', $notlisted));
11420          }
11421  
11422          return true;
11423      }
11424  
11425      /**
11426       * Return an HTML string for the setting element.
11427       *
11428       * @param string $data The current setting value
11429       * @param string $query Admin search query to be highlighted
11430       * @return string HTML to be displayed
11431       */
11432      public function output_html($data, $query='') {
11433          global $OUTPUT, $PAGE;
11434  
11435          $default = $this->get_defaultsetting();
11436          $context = (object) [
11437              'id' => $this->get_id(),
11438              'name' => $this->get_full_name(),
11439              'value' => $data,
11440              'descriptions' => $this->util->describe_file_types($data),
11441          ];
11442          $element = $OUTPUT->render_from_template('core_admin/setting_filetypes', $context);
11443  
11444          $PAGE->requires->js_call_amd('core_form/filetypes', 'init', [
11445              $this->get_id(),
11446              $this->visiblename->out(),
11447              $this->onlytypes,
11448              $this->allowall,
11449          ]);
11450  
11451          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
11452      }
11453  
11454      /**
11455       * Should the values be always displayed in LTR mode?
11456       *
11457       * We always return true here because these values are not RTL compatible.
11458       *
11459       * @return bool True because these values are not RTL compatible.
11460       */
11461      public function get_force_ltr() {
11462          return true;
11463      }
11464  }
11465  
11466  /**
11467   * Used to validate the content and format of the age of digital consent map and ensuring it is parsable.
11468   *
11469   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11470   * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
11471   */
11472  class admin_setting_agedigitalconsentmap extends admin_setting_configtextarea {
11473  
11474      /**
11475       * Constructor.
11476       *
11477       * @param string $name
11478       * @param string $visiblename
11479       * @param string $description
11480       * @param mixed $defaultsetting string or array
11481       * @param mixed $paramtype
11482       * @param string $cols
11483       * @param string $rows
11484       */
11485      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype = PARAM_RAW,
11486                                  $cols = '60', $rows = '8') {
11487          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
11488          // Pre-set force LTR to false.
11489          $this->set_force_ltr(false);
11490      }
11491  
11492      /**
11493       * Validate the content and format of the age of digital consent map to ensure it is parsable.
11494       *
11495       * @param string $data The age of digital consent map from text field.
11496       * @return mixed bool true for success or string:error on failure.
11497       */
11498      public function validate($data) {
11499          if (empty($data)) {
11500              return true;
11501          }
11502  
11503          try {
11504              \core_auth\digital_consent::parse_age_digital_consent_map($data);
11505          } catch (\moodle_exception $e) {
11506              return get_string('invalidagedigitalconsent', 'admin', $e->getMessage());
11507          }
11508  
11509          return true;
11510      }
11511  }
11512  
11513  /**
11514   * Selection of plugins that can work as site policy handlers
11515   *
11516   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11517   * @copyright 2018 Marina Glancy
11518   */
11519  class admin_settings_sitepolicy_handler_select extends admin_setting_configselect {
11520  
11521      /**
11522       * Constructor
11523       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11524       *        for ones in config_plugins.
11525       * @param string $visiblename localised
11526       * @param string $description long localised info
11527       * @param string $defaultsetting
11528       */
11529      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11530          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11531      }
11532  
11533      /**
11534       * Lazy-load the available choices for the select box
11535       */
11536      public function load_choices() {
11537          if (during_initial_install()) {
11538              return false;
11539          }
11540          if (is_array($this->choices)) {
11541              return true;
11542          }
11543  
11544          $this->choices = ['' => new lang_string('sitepolicyhandlercore', 'core_admin')];
11545          $manager = new \core_privacy\local\sitepolicy\manager();
11546          $plugins = $manager->get_all_handlers();
11547          foreach ($plugins as $pname => $unused) {
11548              $this->choices[$pname] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11549                  ['name' => new lang_string('pluginname', $pname), 'component' => $pname]);
11550          }
11551  
11552          return true;
11553      }
11554  }
11555  
11556  /**
11557   * Used to validate theme presets code and ensuring they compile well.
11558   *
11559   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11560   * @copyright 2019 Bas Brands <bas@moodle.com>
11561   */
11562  class admin_setting_configthemepreset extends admin_setting_configselect {
11563  
11564      /** @var string The name of the theme to check for */
11565      private $themename;
11566  
11567      /**
11568       * Constructor
11569       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
11570       * or 'myplugin/mysetting' for ones in config_plugins.
11571       * @param string $visiblename localised
11572       * @param string $description long localised info
11573       * @param string|int $defaultsetting
11574       * @param array $choices array of $value=>$label for each selection
11575       * @param string $themename name of theme to check presets for.
11576       */
11577      public function __construct($name, $visiblename, $description, $defaultsetting, $choices, $themename) {
11578          $this->themename = $themename;
11579          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
11580      }
11581  
11582      /**
11583       * Write settings if validated
11584       *
11585       * @param string $data
11586       * @return string
11587       */
11588      public function write_setting($data) {
11589          $validated = $this->validate($data);
11590          if ($validated !== true) {
11591              return $validated;
11592          }
11593          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
11594      }
11595  
11596      /**
11597       * Validate the preset file to ensure its parsable.
11598       *
11599       * @param string $data The preset file chosen.
11600       * @return mixed bool true for success or string:error on failure.
11601       */
11602      public function validate($data) {
11603  
11604          if (in_array($data, ['default.scss', 'plain.scss'])) {
11605              return true;
11606          }
11607  
11608          $fs = get_file_storage();
11609          $theme = theme_config::load($this->themename);
11610          $context = context_system::instance();
11611  
11612          // If the preset has not changed there is no need to validate it.
11613          if ($theme->settings->preset == $data) {
11614              return true;
11615          }
11616  
11617          if ($presetfile = $fs->get_file($context->id, 'theme_' . $this->themename, 'preset', 0, '/', $data)) {
11618              // This operation uses a lot of resources.
11619              raise_memory_limit(MEMORY_EXTRA);
11620              core_php_time_limit::raise(300);
11621  
11622              // TODO: MDL-62757 When changing anything in this method please do not forget to check
11623              // if the get_css_content_from_scss() method in class theme_config needs updating too.
11624  
11625              $compiler = new core_scss();
11626              $compiler->prepend_raw_scss($theme->get_pre_scss_code());
11627              $compiler->append_raw_scss($presetfile->get_content());
11628              if ($scssproperties = $theme->get_scss_property()) {
11629                  $compiler->setImportPaths($scssproperties[0]);
11630              }
11631              $compiler->append_raw_scss($theme->get_extra_scss_code());
11632  
11633              try {
11634                  $compiler->to_css();
11635              } catch (Exception $e) {
11636                  return get_string('invalidthemepreset', 'admin', $e->getMessage());
11637              }
11638  
11639              // Try to save memory.
11640              $compiler = null;
11641              unset($compiler);
11642          }
11643  
11644          return true;
11645      }
11646  }
11647  
11648  /**
11649   * Selection of plugins that can work as H5P libraries handlers
11650   *
11651   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11652   * @copyright 2020 Sara Arjona <sara@moodle.com>
11653   */
11654  class admin_settings_h5plib_handler_select extends admin_setting_configselect {
11655  
11656      /**
11657       * Constructor
11658       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11659       *        for ones in config_plugins.
11660       * @param string $visiblename localised
11661       * @param string $description long localised info
11662       * @param string $defaultsetting
11663       */
11664      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11665          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11666      }
11667  
11668      /**
11669       * Lazy-load the available choices for the select box
11670       */
11671      public function load_choices() {
11672          if (during_initial_install()) {
11673              return false;
11674          }
11675          if (is_array($this->choices)) {
11676              return true;
11677          }
11678  
11679          $this->choices = \core_h5p\local\library\autoloader::get_all_handlers();
11680          foreach ($this->choices as $name => $class) {
11681              $this->choices[$name] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11682                  ['name' => new lang_string('pluginname', $name), 'component' => $name]);
11683          }
11684  
11685          return true;
11686      }
11687  }