Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
/lib/ -> adminlib.php (source)

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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      \core_external\util::delete_service_descriptions($component);
 212  
 213      // delete calendar events
 214      $DB->delete_records('event', array('modulename' => $pluginname));
 215      $DB->delete_records('event', ['component' => $component]);
 216  
 217      // Delete scheduled tasks.
 218      $DB->delete_records('task_adhoc', ['component' => $component]);
 219      $DB->delete_records('task_scheduled', array('component' => $component));
 220  
 221      // Delete Inbound Message datakeys.
 222      $DB->delete_records_select('messageinbound_datakeys',
 223              'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($component));
 224  
 225      // Delete Inbound Message handlers.
 226      $DB->delete_records('messageinbound_handlers', array('component' => $component));
 227  
 228      // delete all the logs
 229      $DB->delete_records('log', array('module' => $pluginname));
 230  
 231      // delete log_display information
 232      $DB->delete_records('log_display', array('component' => $component));
 233  
 234      // delete the module configuration records
 235      unset_all_config_for_plugin($component);
 236      if ($type === 'mod') {
 237          unset_all_config_for_plugin($pluginname);
 238      }
 239  
 240      // Wipe any xAPI state information.
 241      if (core_xapi\handler::supports_xapi($component)) {
 242          core_xapi\api::remove_states_from_component($component);
 243      }
 244  
 245      // delete message provider
 246      message_provider_uninstall($component);
 247  
 248      // delete the plugin tables
 249      $xmldbfilepath = $plugindirectory . '/db/install.xml';
 250      drop_plugin_tables($component, $xmldbfilepath, false);
 251      if ($type === 'mod' or $type === 'block') {
 252          // non-frankenstyle table prefixes
 253          drop_plugin_tables($name, $xmldbfilepath, false);
 254      }
 255  
 256      // delete the capabilities that were defined by this module
 257      capabilities_cleanup($component);
 258  
 259      // Delete all remaining files in the filepool owned by the component.
 260      $fs = get_file_storage();
 261      $fs->delete_component_files($component);
 262  
 263      // Finally purge all caches.
 264      purge_all_caches();
 265  
 266      // Invalidate the hash used for upgrade detections.
 267      set_config('allversionshash', '');
 268  
 269      echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
 270  }
 271  
 272  /**
 273   * Returns the version of installed component
 274   *
 275   * @param string $component component name
 276   * @param string $source either 'disk' or 'installed' - where to get the version information from
 277   * @return string|bool version number or false if the component is not found
 278   */
 279  function get_component_version($component, $source='installed') {
 280      global $CFG, $DB;
 281  
 282      list($type, $name) = core_component::normalize_component($component);
 283  
 284      // moodle core or a core subsystem
 285      if ($type === 'core') {
 286          if ($source === 'installed') {
 287              if (empty($CFG->version)) {
 288                  return false;
 289              } else {
 290                  return $CFG->version;
 291              }
 292          } else {
 293              if (!is_readable($CFG->dirroot.'/version.php')) {
 294                  return false;
 295              } else {
 296                  $version = null; //initialize variable for IDEs
 297                  include($CFG->dirroot.'/version.php');
 298                  return $version;
 299              }
 300          }
 301      }
 302  
 303      // activity module
 304      if ($type === 'mod') {
 305          if ($source === 'installed') {
 306              if ($CFG->version < 2013092001.02) {
 307                  return $DB->get_field('modules', 'version', array('name'=>$name));
 308              } else {
 309                  return get_config('mod_'.$name, 'version');
 310              }
 311  
 312          } else {
 313              $mods = core_component::get_plugin_list('mod');
 314              if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
 315                  return false;
 316              } else {
 317                  $plugin = new stdClass();
 318                  $plugin->version = null;
 319                  $module = $plugin;
 320                  include($mods[$name].'/version.php');
 321                  return $plugin->version;
 322              }
 323          }
 324      }
 325  
 326      // block
 327      if ($type === 'block') {
 328          if ($source === 'installed') {
 329              if ($CFG->version < 2013092001.02) {
 330                  return $DB->get_field('block', 'version', array('name'=>$name));
 331              } else {
 332                  return get_config('block_'.$name, 'version');
 333              }
 334          } else {
 335              $blocks = core_component::get_plugin_list('block');
 336              if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
 337                  return false;
 338              } else {
 339                  $plugin = new stdclass();
 340                  include($blocks[$name].'/version.php');
 341                  return $plugin->version;
 342              }
 343          }
 344      }
 345  
 346      // all other plugin types
 347      if ($source === 'installed') {
 348          return get_config($type.'_'.$name, 'version');
 349      } else {
 350          $plugins = core_component::get_plugin_list($type);
 351          if (empty($plugins[$name])) {
 352              return false;
 353          } else {
 354              $plugin = new stdclass();
 355              include($plugins[$name].'/version.php');
 356              return $plugin->version;
 357          }
 358      }
 359  }
 360  
 361  /**
 362   * Delete all plugin tables
 363   *
 364   * @param string $name Name of plugin, used as table prefix
 365   * @param string $file Path to install.xml file
 366   * @param bool $feedback defaults to true
 367   * @return bool Always returns true
 368   */
 369  function drop_plugin_tables($name, $file, $feedback=true) {
 370      global $CFG, $DB;
 371  
 372      // first try normal delete
 373      if (file_exists($file)) {
 374          $DB->get_manager()->delete_tables_from_xmldb_file($file);
 375          return true;
 376      }
 377  
 378      // then try to find all tables that start with name and are not in any xml file
 379      $used_tables = get_used_table_names();
 380  
 381      $tables = $DB->get_tables();
 382  
 383      /// Iterate over, fixing id fields as necessary
 384      foreach ($tables as $table) {
 385          if (in_array($table, $used_tables)) {
 386              continue;
 387          }
 388  
 389          if (strpos($table, $name) !== 0) {
 390              continue;
 391          }
 392  
 393          // found orphan table --> delete it
 394          if ($DB->get_manager()->table_exists($table)) {
 395              $xmldb_table = new xmldb_table($table);
 396              $DB->get_manager()->drop_table($xmldb_table);
 397          }
 398      }
 399  
 400      return true;
 401  }
 402  
 403  /**
 404   * Returns names of all known tables == tables that moodle knows about.
 405   *
 406   * @return array Array of lowercase table names
 407   */
 408  function get_used_table_names() {
 409      $table_names = array();
 410      $dbdirs = get_db_directories();
 411  
 412      foreach ($dbdirs as $dbdir) {
 413          $file = $dbdir.'/install.xml';
 414  
 415          $xmldb_file = new xmldb_file($file);
 416  
 417          if (!$xmldb_file->fileExists()) {
 418              continue;
 419          }
 420  
 421          $loaded    = $xmldb_file->loadXMLStructure();
 422          $structure = $xmldb_file->getStructure();
 423  
 424          if ($loaded and $tables = $structure->getTables()) {
 425              foreach($tables as $table) {
 426                  $table_names[] = strtolower($table->getName());
 427              }
 428          }
 429      }
 430  
 431      return $table_names;
 432  }
 433  
 434  /**
 435   * Returns list of all directories where we expect install.xml files
 436   * @return array Array of paths
 437   */
 438  function get_db_directories() {
 439      global $CFG;
 440  
 441      $dbdirs = array();
 442  
 443      /// First, the main one (lib/db)
 444      $dbdirs[] = $CFG->libdir.'/db';
 445  
 446      /// Then, all the ones defined by core_component::get_plugin_types()
 447      $plugintypes = core_component::get_plugin_types();
 448      foreach ($plugintypes as $plugintype => $pluginbasedir) {
 449          if ($plugins = core_component::get_plugin_list($plugintype)) {
 450              foreach ($plugins as $plugin => $plugindir) {
 451                  $dbdirs[] = $plugindir.'/db';
 452              }
 453          }
 454      }
 455  
 456      return $dbdirs;
 457  }
 458  
 459  /**
 460   * Try to obtain or release the cron lock.
 461   * @param string  $name  name of lock
 462   * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionally
 463   * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
 464   * @return bool true if lock obtained
 465   */
 466  function set_cron_lock($name, $until, $ignorecurrent=false) {
 467      global $DB;
 468      if (empty($name)) {
 469          debugging("Tried to get a cron lock for a null fieldname");
 470          return false;
 471      }
 472  
 473      // remove lock by force == remove from config table
 474      if (is_null($until)) {
 475          set_config($name, null);
 476          return true;
 477      }
 478  
 479      if (!$ignorecurrent) {
 480          // read value from db - other processes might have changed it
 481          $value = $DB->get_field('config', 'value', array('name'=>$name));
 482  
 483          if ($value and $value > time()) {
 484              //lock active
 485              return false;
 486          }
 487      }
 488  
 489      set_config($name, $until);
 490      return true;
 491  }
 492  
 493  /**
 494   * Test if and critical warnings are present
 495   * @return bool
 496   */
 497  function admin_critical_warnings_present() {
 498      global $SESSION;
 499  
 500      if (!has_capability('moodle/site:config', context_system::instance())) {
 501          return 0;
 502      }
 503  
 504      if (!isset($SESSION->admin_critical_warning)) {
 505          $SESSION->admin_critical_warning = 0;
 506          if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
 507              $SESSION->admin_critical_warning = 1;
 508          }
 509      }
 510  
 511      return $SESSION->admin_critical_warning;
 512  }
 513  
 514  /**
 515   * Detects if float supports at least 10 decimal digits
 516   *
 517   * Detects if float supports at least 10 decimal digits
 518   * and also if float-->string conversion works as expected.
 519   *
 520   * @return bool true if problem found
 521   */
 522  function is_float_problem() {
 523      $num1 = 2009010200.01;
 524      $num2 = 2009010200.02;
 525  
 526      return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
 527  }
 528  
 529  /**
 530   * Try to verify that dataroot is not accessible from web.
 531   *
 532   * Try to verify that dataroot is not accessible from web.
 533   * It is not 100% correct but might help to reduce number of vulnerable sites.
 534   * Protection from httpd.conf and .htaccess is not detected properly.
 535   *
 536   * @uses INSECURE_DATAROOT_WARNING
 537   * @uses INSECURE_DATAROOT_ERROR
 538   * @param bool $fetchtest try to test public access by fetching file, default false
 539   * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
 540   */
 541  function is_dataroot_insecure($fetchtest=false) {
 542      global $CFG;
 543  
 544      $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
 545  
 546      $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
 547      $rp = strrev(trim($rp, '/'));
 548      $rp = explode('/', $rp);
 549      foreach($rp as $r) {
 550          if (strpos($siteroot, '/'.$r.'/') === 0) {
 551              $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
 552          } else {
 553              break; // probably alias root
 554          }
 555      }
 556  
 557      $siteroot = strrev($siteroot);
 558      $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
 559  
 560      if (strpos($dataroot, $siteroot) !== 0) {
 561          return false;
 562      }
 563  
 564      if (!$fetchtest) {
 565          return INSECURE_DATAROOT_WARNING;
 566      }
 567  
 568      // now try all methods to fetch a test file using http protocol
 569  
 570      $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
 571      preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
 572      $httpdocroot = $matches[1];
 573      $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
 574      make_upload_directory('diag');
 575      $testfile = $CFG->dataroot.'/diag/public.txt';
 576      if (!file_exists($testfile)) {
 577          file_put_contents($testfile, 'test file, do not delete');
 578          @chmod($testfile, $CFG->filepermissions);
 579      }
 580      $teststr = trim(file_get_contents($testfile));
 581      if (empty($teststr)) {
 582      // hmm, strange
 583          return INSECURE_DATAROOT_WARNING;
 584      }
 585  
 586      $testurl = $datarooturl.'/diag/public.txt';
 587      if (extension_loaded('curl') and
 588          !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
 589          !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
 590          ($ch = @curl_init($testurl)) !== false) {
 591          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 592          curl_setopt($ch, CURLOPT_HEADER, false);
 593          $data = curl_exec($ch);
 594          if (!curl_errno($ch)) {
 595              $data = trim($data);
 596              if ($data === $teststr) {
 597                  curl_close($ch);
 598                  return INSECURE_DATAROOT_ERROR;
 599              }
 600          }
 601          curl_close($ch);
 602      }
 603  
 604      if ($data = @file_get_contents($testurl)) {
 605          $data = trim($data);
 606          if ($data === $teststr) {
 607              return INSECURE_DATAROOT_ERROR;
 608          }
 609      }
 610  
 611      preg_match('|https?://([^/]+)|i', $testurl, $matches);
 612      $sitename = $matches[1];
 613      $error = 0;
 614      if ($fp = @fsockopen($sitename, 80, $error)) {
 615          preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
 616          $localurl = $matches[1];
 617          $out = "GET $localurl HTTP/1.1\r\n";
 618          $out .= "Host: $sitename\r\n";
 619          $out .= "Connection: Close\r\n\r\n";
 620          fwrite($fp, $out);
 621          $data = '';
 622          $incoming = false;
 623          while (!feof($fp)) {
 624              if ($incoming) {
 625                  $data .= fgets($fp, 1024);
 626              } else if (@fgets($fp, 1024) === "\r\n") {
 627                      $incoming = true;
 628                  }
 629          }
 630          fclose($fp);
 631          $data = trim($data);
 632          if ($data === $teststr) {
 633              return INSECURE_DATAROOT_ERROR;
 634          }
 635      }
 636  
 637      return INSECURE_DATAROOT_WARNING;
 638  }
 639  
 640  /**
 641   * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
 642   */
 643  function enable_cli_maintenance_mode() {
 644      global $CFG, $SITE;
 645  
 646      if (file_exists("$CFG->dataroot/climaintenance.html")) {
 647          unlink("$CFG->dataroot/climaintenance.html");
 648      }
 649  
 650      if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
 651          $data = $CFG->maintenance_message;
 652          $data = bootstrap_renderer::early_error_content($data, null, null, null);
 653          $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
 654  
 655      } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
 656          $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
 657  
 658      } else {
 659          $data = get_string('sitemaintenance', 'admin');
 660          $data = bootstrap_renderer::early_error_content($data, null, null, null);
 661          $data = bootstrap_renderer::plain_page(get_string('sitemaintenancetitle', 'admin',
 662              format_string($SITE->fullname, true, ['context' => context_system::instance()])), $data);
 663      }
 664  
 665      file_put_contents("$CFG->dataroot/climaintenance.html", $data);
 666      chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
 667  }
 668  
 669  /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
 670  
 671  
 672  /**
 673   * Interface for anything appearing in the admin tree
 674   *
 675   * The interface that is implemented by anything that appears in the admin tree
 676   * block. It forces inheriting classes to define a method for checking user permissions
 677   * and methods for finding something in the admin tree.
 678   *
 679   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 680   */
 681  interface part_of_admin_tree {
 682  
 683  /**
 684   * Finds a named part_of_admin_tree.
 685   *
 686   * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
 687   * and not parentable_part_of_admin_tree, then this function should only check if
 688   * $this->name matches $name. If it does, it should return a reference to $this,
 689   * otherwise, it should return a reference to NULL.
 690   *
 691   * If a class inherits parentable_part_of_admin_tree, this method should be called
 692   * recursively on all child objects (assuming, of course, the parent object's name
 693   * doesn't match the search criterion).
 694   *
 695   * @param string $name The internal name of the part_of_admin_tree we're searching for.
 696   * @return mixed An object reference or a NULL reference.
 697   */
 698      public function locate($name);
 699  
 700      /**
 701       * Removes named part_of_admin_tree.
 702       *
 703       * @param string $name The internal name of the part_of_admin_tree we want to remove.
 704       * @return bool success.
 705       */
 706      public function prune($name);
 707  
 708      /**
 709       * Search using query
 710       * @param string $query
 711       * @return mixed array-object structure of found settings and pages
 712       */
 713      public function search($query);
 714  
 715      /**
 716       * Verifies current user's access to this part_of_admin_tree.
 717       *
 718       * Used to check if the current user has access to this part of the admin tree or
 719       * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
 720       * then this method is usually just a call to has_capability() in the site context.
 721       *
 722       * If a class inherits parentable_part_of_admin_tree, this method should return the
 723       * logical OR of the return of check_access() on all child objects.
 724       *
 725       * @return bool True if the user has access, false if she doesn't.
 726       */
 727      public function check_access();
 728  
 729      /**
 730       * Mostly useful for removing of some parts of the tree in admin tree block.
 731       *
 732       * @return True is hidden from normal list view
 733       */
 734      public function is_hidden();
 735  
 736      /**
 737       * Show we display Save button at the page bottom?
 738       * @return bool
 739       */
 740      public function show_save();
 741  }
 742  
 743  
 744  /**
 745   * Interface implemented by any part_of_admin_tree that has children.
 746   *
 747   * The interface implemented by any part_of_admin_tree that can be a parent
 748   * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
 749   * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
 750   * include an add method for adding other part_of_admin_tree objects as children.
 751   *
 752   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 753   */
 754  interface parentable_part_of_admin_tree extends part_of_admin_tree {
 755  
 756  /**
 757   * Adds a part_of_admin_tree object to the admin tree.
 758   *
 759   * Used to add a part_of_admin_tree object to this object or a child of this
 760   * object. $something should only be added if $destinationname matches
 761   * $this->name. If it doesn't, add should be called on child objects that are
 762   * also parentable_part_of_admin_tree's.
 763   *
 764   * $something should be appended as the last child in the $destinationname. If the
 765   * $beforesibling is specified, $something should be prepended to it. If the given
 766   * sibling is not found, $something should be appended to the end of $destinationname
 767   * and a developer debugging message should be displayed.
 768   *
 769   * @param string $destinationname The internal name of the new parent for $something.
 770   * @param part_of_admin_tree $something The object to be added.
 771   * @return bool True on success, false on failure.
 772   */
 773      public function add($destinationname, $something, $beforesibling = null);
 774  
 775  }
 776  
 777  
 778  /**
 779   * The object used to represent folders (a.k.a. categories) in the admin tree block.
 780   *
 781   * Each admin_category object contains a number of part_of_admin_tree objects.
 782   *
 783   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 784   */
 785  class admin_category implements parentable_part_of_admin_tree, linkable_settings_page {
 786  
 787      /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */
 788      protected $children;
 789      /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
 790      public $name;
 791      /** @var string The displayed name for this category. Usually obtained through get_string() */
 792      public $visiblename;
 793      /** @var bool Should this category be hidden in admin tree block? */
 794      public $hidden;
 795      /** @var mixed Either a string or an array or strings */
 796      public $path;
 797      /** @var mixed Either a string or an array or strings */
 798      public $visiblepath;
 799  
 800      /** @var array fast lookup category cache, all categories of one tree point to one cache */
 801      protected $category_cache;
 802  
 803      /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */
 804      protected $sort = false;
 805      /** @var bool If set to true children will be sorted in ascending order. */
 806      protected $sortasc = true;
 807      /** @var bool If set to true sub categories and pages will be split and then sorted.. */
 808      protected $sortsplit = true;
 809      /** @var bool $sorted True if the children have been sorted and don't need resorting */
 810      protected $sorted = false;
 811  
 812      /**
 813       * Constructor for an empty admin category
 814       *
 815       * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
 816       * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
 817       * @param bool $hidden hide category in admin tree block, defaults to false
 818       */
 819      public function __construct($name, $visiblename, $hidden=false) {
 820          $this->children    = array();
 821          $this->name        = $name;
 822          $this->visiblename = $visiblename;
 823          $this->hidden      = $hidden;
 824      }
 825  
 826      /**
 827       * Get the URL to view this settings page.
 828       *
 829       * @return moodle_url
 830       */
 831      public function get_settings_page_url(): moodle_url {
 832          return new moodle_url(
 833              '/admin/category.php',
 834              [
 835                  'category' => $this->name,
 836              ]
 837          );
 838      }
 839  
 840      /**
 841       * Returns a reference to the part_of_admin_tree object with internal name $name.
 842       *
 843       * @param string $name The internal name of the object we want.
 844       * @param bool $findpath initialize path and visiblepath arrays
 845       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
 846       *                  defaults to false
 847       */
 848      public function locate($name, $findpath=false) {
 849          if (!isset($this->category_cache[$this->name])) {
 850              // somebody much have purged the cache
 851              $this->category_cache[$this->name] = $this;
 852          }
 853  
 854          if ($this->name == $name) {
 855              if ($findpath) {
 856                  $this->visiblepath[] = $this->visiblename;
 857                  $this->path[]        = $this->name;
 858              }
 859              return $this;
 860          }
 861  
 862          // quick category lookup
 863          if (!$findpath and isset($this->category_cache[$name])) {
 864              return $this->category_cache[$name];
 865          }
 866  
 867          $return = NULL;
 868          foreach($this->children as $childid=>$unused) {
 869              if ($return = $this->children[$childid]->locate($name, $findpath)) {
 870                  break;
 871              }
 872          }
 873  
 874          if (!is_null($return) and $findpath) {
 875              $return->visiblepath[] = $this->visiblename;
 876              $return->path[]        = $this->name;
 877          }
 878  
 879          return $return;
 880      }
 881  
 882      /**
 883       * Search using query
 884       *
 885       * @param string query
 886       * @return mixed array-object structure of found settings and pages
 887       */
 888      public function search($query) {
 889          $result = array();
 890          foreach ($this->get_children() as $child) {
 891              $subsearch = $child->search($query);
 892              if (!is_array($subsearch)) {
 893                  debugging('Incorrect search result from '.$child->name);
 894                  continue;
 895              }
 896              $result = array_merge($result, $subsearch);
 897          }
 898          return $result;
 899      }
 900  
 901      /**
 902       * Removes part_of_admin_tree object with internal name $name.
 903       *
 904       * @param string $name The internal name of the object we want to remove.
 905       * @return bool success
 906       */
 907      public function prune($name) {
 908  
 909          if ($this->name == $name) {
 910              return false;  //can not remove itself
 911          }
 912  
 913          foreach($this->children as $precedence => $child) {
 914              if ($child->name == $name) {
 915                  // clear cache and delete self
 916                  while($this->category_cache) {
 917                      // delete the cache, but keep the original array address
 918                      array_pop($this->category_cache);
 919                  }
 920                  unset($this->children[$precedence]);
 921                  return true;
 922              } else if ($this->children[$precedence]->prune($name)) {
 923                  return true;
 924              }
 925          }
 926          return false;
 927      }
 928  
 929      /**
 930       * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
 931       *
 932       * By default the new part of the tree is appended as the last child of the parent. You
 933       * can specify a sibling node that the new part should be prepended to. If the given
 934       * sibling is not found, the part is appended to the end (as it would be by default) and
 935       * a developer debugging message is displayed.
 936       *
 937       * @throws coding_exception if the $beforesibling is empty string or is not string at all.
 938       * @param string $destinationame The internal name of the immediate parent that we want for $something.
 939       * @param mixed $something A part_of_admin_tree or setting instance to be added.
 940       * @param string $beforesibling The name of the parent's child the $something should be prepended to.
 941       * @return bool True if successfully added, false if $something can not be added.
 942       */
 943      public function add($parentname, $something, $beforesibling = null) {
 944          global $CFG;
 945  
 946          $parent = $this->locate($parentname);
 947          if (is_null($parent)) {
 948              debugging('parent does not exist!');
 949              return false;
 950          }
 951  
 952          if ($something instanceof part_of_admin_tree) {
 953              if (!($parent instanceof parentable_part_of_admin_tree)) {
 954                  debugging('error - parts of tree can be inserted only into parentable parts');
 955                  return false;
 956              }
 957              if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
 958                  // The name of the node is already used, simply warn the developer that this should not happen.
 959                  // It is intentional to check for the debug level before performing the check.
 960                  debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
 961              }
 962              if (is_null($beforesibling)) {
 963                  // Append $something as the parent's last child.
 964                  $parent->children[] = $something;
 965              } else {
 966                  if (!is_string($beforesibling) or trim($beforesibling) === '') {
 967                      throw new coding_exception('Unexpected value of the beforesibling parameter');
 968                  }
 969                  // Try to find the position of the sibling.
 970                  $siblingposition = null;
 971                  foreach ($parent->children as $childposition => $child) {
 972                      if ($child->name === $beforesibling) {
 973                          $siblingposition = $childposition;
 974                          break;
 975                      }
 976                  }
 977                  if (is_null($siblingposition)) {
 978                      debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
 979                      $parent->children[] = $something;
 980                  } else {
 981                      $parent->children = array_merge(
 982                          array_slice($parent->children, 0, $siblingposition),
 983                          array($something),
 984                          array_slice($parent->children, $siblingposition)
 985                      );
 986                  }
 987              }
 988              if ($something instanceof admin_category) {
 989                  if (isset($this->category_cache[$something->name])) {
 990                      debugging('Duplicate admin category name: '.$something->name);
 991                  } else {
 992                      $this->category_cache[$something->name] = $something;
 993                      $something->category_cache =& $this->category_cache;
 994                      foreach ($something->children as $child) {
 995                          // just in case somebody already added subcategories
 996                          if ($child instanceof admin_category) {
 997                              if (isset($this->category_cache[$child->name])) {
 998                                  debugging('Duplicate admin category name: '.$child->name);
 999                              } else {
1000                                  $this->category_cache[$child->name] = $child;
1001                                  $child->category_cache =& $this->category_cache;
1002                              }
1003                          }
1004                      }
1005                  }
1006              }
1007              return true;
1008  
1009          } else {
1010              debugging('error - can not add this element');
1011              return false;
1012          }
1013  
1014      }
1015  
1016      /**
1017       * Checks if the user has access to anything in this category.
1018       *
1019       * @return bool True if the user has access to at least one child in this category, false otherwise.
1020       */
1021      public function check_access() {
1022          foreach ($this->children as $child) {
1023              if ($child->check_access()) {
1024                  return true;
1025              }
1026          }
1027          return false;
1028      }
1029  
1030      /**
1031       * Is this category hidden in admin tree block?
1032       *
1033       * @return bool True if hidden
1034       */
1035      public function is_hidden() {
1036          return $this->hidden;
1037      }
1038  
1039      /**
1040       * Show we display Save button at the page bottom?
1041       * @return bool
1042       */
1043      public function show_save() {
1044          foreach ($this->children as $child) {
1045              if ($child->show_save()) {
1046                  return true;
1047              }
1048          }
1049          return false;
1050      }
1051  
1052      /**
1053       * Sets sorting on this category.
1054       *
1055       * Please note this function doesn't actually do the sorting.
1056       * It can be called anytime.
1057       * Sorting occurs when the user calls get_children.
1058       * Code using the children array directly won't see the sorted results.
1059       *
1060       * @param bool $sort If set to true children will be sorted, if false they won't be.
1061       * @param bool $asc If true sorting will be ascending, otherwise descending.
1062       * @param bool $split If true we sort pages and sub categories separately.
1063       */
1064      public function set_sorting($sort, $asc = true, $split = true) {
1065          $this->sort = (bool)$sort;
1066          $this->sortasc = (bool)$asc;
1067          $this->sortsplit = (bool)$split;
1068      }
1069  
1070      /**
1071       * Returns the children associated with this category.
1072       *
1073       * @return part_of_admin_tree[]
1074       */
1075      public function get_children() {
1076          // If we should sort and it hasn't already been sorted.
1077          if ($this->sort && !$this->sorted) {
1078              if ($this->sortsplit) {
1079                  $categories = array();
1080                  $pages = array();
1081                  foreach ($this->children as $child) {
1082                      if ($child instanceof admin_category) {
1083                          $categories[] = $child;
1084                      } else {
1085                          $pages[] = $child;
1086                      }
1087                  }
1088                  core_collator::asort_objects_by_property($categories, 'visiblename');
1089                  core_collator::asort_objects_by_property($pages, 'visiblename');
1090                  if (!$this->sortasc) {
1091                      $categories = array_reverse($categories);
1092                      $pages = array_reverse($pages);
1093                  }
1094                  $this->children = array_merge($pages, $categories);
1095              } else {
1096                  core_collator::asort_objects_by_property($this->children, 'visiblename');
1097                  if (!$this->sortasc) {
1098                      $this->children = array_reverse($this->children);
1099                  }
1100              }
1101              $this->sorted = true;
1102          }
1103          return $this->children;
1104      }
1105  
1106      /**
1107       * Magically gets a property from this object.
1108       *
1109       * @param $property
1110       * @return part_of_admin_tree[]
1111       * @throws coding_exception
1112       */
1113      public function __get($property) {
1114          if ($property === 'children') {
1115              return $this->get_children();
1116          }
1117          throw new coding_exception('Invalid property requested.');
1118      }
1119  
1120      /**
1121       * Magically sets a property against this object.
1122       *
1123       * @param string $property
1124       * @param mixed $value
1125       * @throws coding_exception
1126       */
1127      public function __set($property, $value) {
1128          if ($property === 'children') {
1129              $this->sorted = false;
1130              $this->children = $value;
1131          } else {
1132              throw new coding_exception('Invalid property requested.');
1133          }
1134      }
1135  
1136      /**
1137       * Checks if an inaccessible property is set.
1138       *
1139       * @param string $property
1140       * @return bool
1141       * @throws coding_exception
1142       */
1143      public function __isset($property) {
1144          if ($property === 'children') {
1145              return isset($this->children);
1146          }
1147          throw new coding_exception('Invalid property requested.');
1148      }
1149  }
1150  
1151  
1152  /**
1153   * Root of admin settings tree, does not have any parent.
1154   *
1155   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1156   */
1157  class admin_root extends admin_category {
1158  /** @var array List of errors */
1159      public $errors;
1160      /** @var string search query */
1161      public $search;
1162      /** @var bool full tree flag - true means all settings required, false only pages required */
1163      public $fulltree;
1164      /** @var bool flag indicating loaded tree */
1165      public $loaded;
1166      /** @var mixed site custom defaults overriding defaults in settings files*/
1167      public $custom_defaults;
1168  
1169      /**
1170       * @param bool $fulltree true means all settings required,
1171       *                            false only pages required
1172       */
1173      public function __construct($fulltree) {
1174          global $CFG;
1175  
1176          parent::__construct('root', get_string('administration'), false);
1177          $this->errors   = array();
1178          $this->search   = '';
1179          $this->fulltree = $fulltree;
1180          $this->loaded   = false;
1181  
1182          $this->category_cache = array();
1183  
1184          // load custom defaults if found
1185          $this->custom_defaults = null;
1186          $defaultsfile = "$CFG->dirroot/local/defaults.php";
1187          if (is_readable($defaultsfile)) {
1188              $defaults = array();
1189              include($defaultsfile);
1190              if (is_array($defaults) and count($defaults)) {
1191                  $this->custom_defaults = $defaults;
1192              }
1193          }
1194      }
1195  
1196      /**
1197       * Empties children array, and sets loaded to false
1198       *
1199       * @param bool $requirefulltree
1200       */
1201      public function purge_children($requirefulltree) {
1202          $this->children = array();
1203          $this->fulltree = ($requirefulltree || $this->fulltree);
1204          $this->loaded   = false;
1205          //break circular dependencies - this helps PHP 5.2
1206          while($this->category_cache) {
1207              array_pop($this->category_cache);
1208          }
1209          $this->category_cache = array();
1210      }
1211  }
1212  
1213  
1214  /**
1215   * Links external PHP pages into the admin tree.
1216   *
1217   * See detailed usage example at the top of this document (adminlib.php)
1218   *
1219   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1220   */
1221  class admin_externalpage implements part_of_admin_tree, linkable_settings_page {
1222  
1223      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1224      public $name;
1225  
1226      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1227      public $visiblename;
1228  
1229      /** @var string The external URL that we should link to when someone requests this external page. */
1230      public $url;
1231  
1232      /** @var array The role capability/permission a user must have to access this external page. */
1233      public $req_capability;
1234  
1235      /** @var object The context in which capability/permission should be checked, default is site context. */
1236      public $context;
1237  
1238      /** @var bool hidden in admin tree block. */
1239      public $hidden;
1240  
1241      /** @var mixed either string or array of string */
1242      public $path;
1243  
1244      /** @var array list of visible names of page parents */
1245      public $visiblepath;
1246  
1247      /**
1248       * Constructor for adding an external page into the admin tree.
1249       *
1250       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1251       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1252       * @param string $url The external URL that we should link to when someone requests this external page.
1253       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1254       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1255       * @param stdClass $context The context the page relates to. Not sure what happens
1256       *      if you specify something other than system or front page. Defaults to system.
1257       */
1258      public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1259          $this->name        = $name;
1260          $this->visiblename = $visiblename;
1261          $this->url         = $url;
1262          if (is_array($req_capability)) {
1263              $this->req_capability = $req_capability;
1264          } else {
1265              $this->req_capability = array($req_capability);
1266          }
1267          $this->hidden = $hidden;
1268          $this->context = $context;
1269      }
1270  
1271      /**
1272       * Get the URL to view this settings page.
1273       *
1274       * @return moodle_url
1275       */
1276      public function get_settings_page_url(): moodle_url {
1277          return new moodle_url($this->url);
1278      }
1279  
1280      /**
1281       * Returns a reference to the part_of_admin_tree object with internal name $name.
1282       *
1283       * @param string $name The internal name of the object we want.
1284       * @param bool $findpath defaults to false
1285       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1286       */
1287      public function locate($name, $findpath=false) {
1288          if ($this->name == $name) {
1289              if ($findpath) {
1290                  $this->visiblepath = array($this->visiblename);
1291                  $this->path        = array($this->name);
1292              }
1293              return $this;
1294          } else {
1295              $return = NULL;
1296              return $return;
1297          }
1298      }
1299  
1300      /**
1301       * This function always returns false, required function by interface
1302       *
1303       * @param string $name
1304       * @return false
1305       */
1306      public function prune($name) {
1307          return false;
1308      }
1309  
1310      /**
1311       * Search using query
1312       *
1313       * @param string $query
1314       * @return mixed array-object structure of found settings and pages
1315       */
1316      public function search($query) {
1317          $found = false;
1318          if (strpos(strtolower($this->name), $query) !== false) {
1319              $found = true;
1320          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1321                  $found = true;
1322              }
1323          if ($found) {
1324              $result = new stdClass();
1325              $result->page     = $this;
1326              $result->settings = array();
1327              return array($this->name => $result);
1328          } else {
1329              return array();
1330          }
1331      }
1332  
1333      /**
1334       * Determines if the current user has access to this external page based on $this->req_capability.
1335       *
1336       * @return bool True if user has access, false otherwise.
1337       */
1338      public function check_access() {
1339          global $CFG;
1340          $context = empty($this->context) ? context_system::instance() : $this->context;
1341          foreach($this->req_capability as $cap) {
1342              if (has_capability($cap, $context)) {
1343                  return true;
1344              }
1345          }
1346          return false;
1347      }
1348  
1349      /**
1350       * Is this external page hidden in admin tree block?
1351       *
1352       * @return bool True if hidden
1353       */
1354      public function is_hidden() {
1355          return $this->hidden;
1356      }
1357  
1358      /**
1359       * Show we display Save button at the page bottom?
1360       * @return bool
1361       */
1362      public function show_save() {
1363          return false;
1364      }
1365  }
1366  
1367  /**
1368   * Used to store details of the dependency between two settings elements.
1369   *
1370   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1371   * @copyright 2017 Davo Smith, Synergy Learning
1372   */
1373  class admin_settingdependency {
1374      /** @var string the name of the setting to be shown/hidden */
1375      public $settingname;
1376      /** @var string the setting this is dependent on */
1377      public $dependenton;
1378      /** @var string the condition to show/hide the element */
1379      public $condition;
1380      /** @var string the value to compare against */
1381      public $value;
1382  
1383      /** @var string[] list of valid conditions */
1384      private static $validconditions = ['checked', 'notchecked', 'noitemselected', 'eq', 'neq', 'in'];
1385  
1386      /**
1387       * admin_settingdependency constructor.
1388       * @param string $settingname
1389       * @param string $dependenton
1390       * @param string $condition
1391       * @param string $value
1392       * @throws \coding_exception
1393       */
1394      public function __construct($settingname, $dependenton, $condition, $value) {
1395          $this->settingname = $this->parse_name($settingname);
1396          $this->dependenton = $this->parse_name($dependenton);
1397          $this->condition = $condition;
1398          $this->value = $value;
1399  
1400          if (!in_array($this->condition, self::$validconditions)) {
1401              throw new coding_exception("Invalid condition '$condition'");
1402          }
1403      }
1404  
1405      /**
1406       * Convert the setting name into the form field name.
1407       * @param string $name
1408       * @return string
1409       */
1410      private function parse_name($name) {
1411          $bits = explode('/', $name);
1412          $name = array_pop($bits);
1413          $plugin = '';
1414          if ($bits) {
1415              $plugin = array_pop($bits);
1416              if ($plugin === 'moodle') {
1417                  $plugin = '';
1418              }
1419          }
1420          return 's_'.$plugin.'_'.$name;
1421      }
1422  
1423      /**
1424       * Gather together all the dependencies in a format suitable for initialising javascript
1425       * @param admin_settingdependency[] $dependencies
1426       * @return array
1427       */
1428      public static function prepare_for_javascript($dependencies) {
1429          $result = [];
1430          foreach ($dependencies as $d) {
1431              if (!isset($result[$d->dependenton])) {
1432                  $result[$d->dependenton] = [];
1433              }
1434              if (!isset($result[$d->dependenton][$d->condition])) {
1435                  $result[$d->dependenton][$d->condition] = [];
1436              }
1437              if (!isset($result[$d->dependenton][$d->condition][$d->value])) {
1438                  $result[$d->dependenton][$d->condition][$d->value] = [];
1439              }
1440              $result[$d->dependenton][$d->condition][$d->value][] = $d->settingname;
1441          }
1442          return $result;
1443      }
1444  }
1445  
1446  /**
1447   * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1448   *
1449   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1450   */
1451  class admin_settingpage implements part_of_admin_tree, linkable_settings_page {
1452  
1453      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1454      public $name;
1455  
1456      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1457      public $visiblename;
1458  
1459      /** @var mixed An array of admin_setting objects that are part of this setting page. */
1460      public $settings;
1461  
1462      /** @var admin_settingdependency[] list of settings to hide when certain conditions are met */
1463      protected $dependencies = [];
1464  
1465      /** @var array The role capability/permission a user must have to access this external page. */
1466      public $req_capability;
1467  
1468      /** @var object The context in which capability/permission should be checked, default is site context. */
1469      public $context;
1470  
1471      /** @var bool hidden in admin tree block. */
1472      public $hidden;
1473  
1474      /** @var mixed string of paths or array of strings of paths */
1475      public $path;
1476  
1477      /** @var array list of visible names of page parents */
1478      public $visiblepath;
1479  
1480      /**
1481       * see admin_settingpage for details of this function
1482       *
1483       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1484       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1485       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1486       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1487       * @param stdClass $context The context the page relates to. Not sure what happens
1488       *      if you specify something other than system or front page. Defaults to system.
1489       */
1490      public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1491          $this->settings    = new stdClass();
1492          $this->name        = $name;
1493          $this->visiblename = $visiblename;
1494          if (is_array($req_capability)) {
1495              $this->req_capability = $req_capability;
1496          } else {
1497              $this->req_capability = array($req_capability);
1498          }
1499          $this->hidden      = $hidden;
1500          $this->context     = $context;
1501      }
1502  
1503      /**
1504       * Get the URL to view this page.
1505       *
1506       * @return moodle_url
1507       */
1508      public function get_settings_page_url(): moodle_url {
1509          return new moodle_url(
1510              '/admin/settings.php',
1511              [
1512                  'section' => $this->name,
1513              ]
1514          );
1515      }
1516  
1517      /**
1518       * see admin_category
1519       *
1520       * @param string $name
1521       * @param bool $findpath
1522       * @return mixed Object (this) if name ==  this->name, else returns null
1523       */
1524      public function locate($name, $findpath=false) {
1525          if ($this->name == $name) {
1526              if ($findpath) {
1527                  $this->visiblepath = array($this->visiblename);
1528                  $this->path        = array($this->name);
1529              }
1530              return $this;
1531          } else {
1532              $return = NULL;
1533              return $return;
1534          }
1535      }
1536  
1537      /**
1538       * Search string in settings page.
1539       *
1540       * @param string $query
1541       * @return array
1542       */
1543      public function search($query) {
1544          $found = array();
1545  
1546          foreach ($this->settings as $setting) {
1547              if ($setting->is_related($query)) {
1548                  $found[] = $setting;
1549              }
1550          }
1551  
1552          if ($found) {
1553              $result = new stdClass();
1554              $result->page     = $this;
1555              $result->settings = $found;
1556              return array($this->name => $result);
1557          }
1558  
1559          $found = false;
1560          if (strpos(strtolower($this->name), $query) !== false) {
1561              $found = true;
1562          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1563                  $found = true;
1564              }
1565          if ($found) {
1566              $result = new stdClass();
1567              $result->page     = $this;
1568              $result->settings = array();
1569              return array($this->name => $result);
1570          } else {
1571              return array();
1572          }
1573      }
1574  
1575      /**
1576       * This function always returns false, required by interface
1577       *
1578       * @param string $name
1579       * @return bool Always false
1580       */
1581      public function prune($name) {
1582          return false;
1583      }
1584  
1585      /**
1586       * adds an admin_setting to this admin_settingpage
1587       *
1588       * 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
1589       * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1590       *
1591       * @param object $setting is the admin_setting object you want to add
1592       * @return bool true if successful, false if not
1593       */
1594      public function add($setting) {
1595          if (!($setting instanceof admin_setting)) {
1596              debugging('error - not a setting instance');
1597              return false;
1598          }
1599  
1600          $name = $setting->name;
1601          if ($setting->plugin) {
1602              $name = $setting->plugin . $name;
1603          }
1604          $this->settings->{$name} = $setting;
1605          return true;
1606      }
1607  
1608      /**
1609       * Hide the named setting if the specified condition is matched.
1610       *
1611       * @param string $settingname
1612       * @param string $dependenton
1613       * @param string $condition
1614       * @param string $value
1615       */
1616      public function hide_if($settingname, $dependenton, $condition = 'notchecked', $value = '1') {
1617          $this->dependencies[] = new admin_settingdependency($settingname, $dependenton, $condition, $value);
1618  
1619          // Reformat the dependency name to the plugin | name format used in the display.
1620          $dependenton = str_replace('/', ' | ', $dependenton);
1621  
1622          // Let the setting know, so it can be displayed underneath.
1623          $findname = str_replace('/', '', $settingname);
1624          foreach ($this->settings as $name => $setting) {
1625              if ($name === $findname) {
1626                  $setting->add_dependent_on($dependenton);
1627              }
1628          }
1629      }
1630  
1631      /**
1632       * see admin_externalpage
1633       *
1634       * @return bool Returns true for yes false for no
1635       */
1636      public function check_access() {
1637          global $CFG;
1638          $context = empty($this->context) ? context_system::instance() : $this->context;
1639          foreach($this->req_capability as $cap) {
1640              if (has_capability($cap, $context)) {
1641                  return true;
1642              }
1643          }
1644          return false;
1645      }
1646  
1647      /**
1648       * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1649       * @return string Returns an XHTML string
1650       */
1651      public function output_html() {
1652          $adminroot = admin_get_root();
1653          $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1654          foreach($this->settings as $setting) {
1655              $fullname = $setting->get_full_name();
1656              if (array_key_exists($fullname, $adminroot->errors)) {
1657                  $data = $adminroot->errors[$fullname]->data;
1658              } else {
1659                  $data = $setting->get_setting();
1660                  // do not use defaults if settings not available - upgrade settings handles the defaults!
1661              }
1662              $return .= $setting->output_html($data);
1663          }
1664          $return .= '</fieldset>';
1665          return $return;
1666      }
1667  
1668      /**
1669       * Is this settings page hidden in admin tree block?
1670       *
1671       * @return bool True if hidden
1672       */
1673      public function is_hidden() {
1674          return $this->hidden;
1675      }
1676  
1677      /**
1678       * Show we display Save button at the page bottom?
1679       * @return bool
1680       */
1681      public function show_save() {
1682          foreach($this->settings as $setting) {
1683              if (empty($setting->nosave)) {
1684                  return true;
1685              }
1686          }
1687          return false;
1688      }
1689  
1690      /**
1691       * Should any of the settings on this page be shown / hidden based on conditions?
1692       * @return bool
1693       */
1694      public function has_dependencies() {
1695          return (bool)$this->dependencies;
1696      }
1697  
1698      /**
1699       * Format the setting show/hide conditions ready to initialise the page javascript
1700       * @return array
1701       */
1702      public function get_dependencies_for_javascript() {
1703          if (!$this->has_dependencies()) {
1704              return [];
1705          }
1706          return admin_settingdependency::prepare_for_javascript($this->dependencies);
1707      }
1708  }
1709  
1710  
1711  /**
1712   * Admin settings class. Only exists on setting pages.
1713   * Read & write happens at this level; no authentication.
1714   *
1715   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1716   */
1717  abstract class admin_setting {
1718      /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1719      public $name;
1720      /** @var lang_string|string localised name */
1721      public $visiblename;
1722      /** @var string localised long description in Markdown format */
1723      public $description;
1724      /** @var mixed Can be string or array of string */
1725      public $defaultsetting;
1726      /** @var string */
1727      public $updatedcallback;
1728      /** @var mixed can be String or Null.  Null means main config table */
1729      public $plugin; // null means main config table
1730      /** @var bool true indicates this setting does not actually save anything, just information */
1731      public $nosave = false;
1732      /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1733      public $affectsmodinfo = false;
1734      /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */
1735      private $flags = array();
1736      /** @var bool Whether this field must be forced LTR. */
1737      private $forceltr = null;
1738      /** @var array list of other settings that may cause this setting to be hidden */
1739      private $dependenton = [];
1740      /** @var bool Whether this setting uses a custom form control */
1741      protected $customcontrol = false;
1742      /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
1743      public $paramtype;
1744  
1745      /**
1746       * Constructor
1747       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1748       *                     or 'myplugin/mysetting' for ones in config_plugins.
1749       * @param string $visiblename localised name
1750       * @param string $description localised long description
1751       * @param mixed $defaultsetting string or array depending on implementation
1752       */
1753      public function __construct($name, $visiblename, $description, $defaultsetting) {
1754          $this->parse_setting_name($name);
1755          $this->visiblename    = $visiblename;
1756          $this->description    = $description;
1757          $this->defaultsetting = $defaultsetting;
1758      }
1759  
1760      /**
1761       * Generic function to add a flag to this admin setting.
1762       *
1763       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1764       * @param bool $default - The default for the flag
1765       * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name.
1766       * @param string $displayname - The display name for this flag. Used as a label next to the checkbox.
1767       */
1768      protected function set_flag_options($enabled, $default, $shortname, $displayname) {
1769          if (empty($this->flags[$shortname])) {
1770              $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname);
1771          } else {
1772              $this->flags[$shortname]->set_options($enabled, $default);
1773          }
1774      }
1775  
1776      /**
1777       * Set the enabled options flag on this admin setting.
1778       *
1779       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1780       * @param bool $default - The default for the flag
1781       */
1782      public function set_enabled_flag_options($enabled, $default) {
1783          $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin'));
1784      }
1785  
1786      /**
1787       * Set the advanced options flag on this admin setting.
1788       *
1789       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1790       * @param bool $default - The default for the flag
1791       */
1792      public function set_advanced_flag_options($enabled, $default) {
1793          $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced'));
1794      }
1795  
1796  
1797      /**
1798       * Set the locked options flag on this admin setting.
1799       *
1800       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1801       * @param bool $default - The default for the flag
1802       */
1803      public function set_locked_flag_options($enabled, $default) {
1804          $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
1805      }
1806  
1807      /**
1808       * Set the required options flag on this admin setting.
1809       *
1810       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED.
1811       * @param bool $default - The default for the flag.
1812       */
1813      public function set_required_flag_options($enabled, $default) {
1814          $this->set_flag_options($enabled, $default, 'required', new lang_string('required', 'core_admin'));
1815      }
1816  
1817      /**
1818       * Is this option forced in config.php?
1819       *
1820       * @return bool
1821       */
1822      public function is_readonly(): bool {
1823          global $CFG;
1824  
1825          if (empty($this->plugin)) {
1826              if ($this->is_forceable() && array_key_exists($this->name, $CFG->config_php_settings)) {
1827                  return true;
1828              }
1829          } else {
1830              if (array_key_exists($this->plugin, $CFG->forced_plugin_settings)
1831                  and array_key_exists($this->name, $CFG->forced_plugin_settings[$this->plugin])) {
1832                  return true;
1833              }
1834          }
1835          return false;
1836      }
1837  
1838      /**
1839       * Get the currently saved value for a setting flag
1840       *
1841       * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting.
1842       * @return bool
1843       */
1844      public function get_setting_flag_value(admin_setting_flag $flag) {
1845          $value = $this->config_read($this->name . '_' . $flag->get_shortname());
1846          if (!isset($value)) {
1847              $value = $flag->get_default();
1848          }
1849  
1850          return !empty($value);
1851      }
1852  
1853      /**
1854       * Get the list of defaults for the flags on this setting.
1855       *
1856       * @param array of strings describing the defaults for this setting. This is appended to by this function.
1857       */
1858      public function get_setting_flag_defaults(& $defaults) {
1859          foreach ($this->flags as $flag) {
1860              if ($flag->is_enabled() && $flag->get_default()) {
1861                  $defaults[] = $flag->get_displayname();
1862              }
1863          }
1864      }
1865  
1866      /**
1867       * Output the input fields for the advanced and locked flags on this setting.
1868       *
1869       * @param bool $adv - The current value of the advanced flag.
1870       * @param bool $locked - The current value of the locked flag.
1871       * @return string $output - The html for the flags.
1872       */
1873      public function output_setting_flags() {
1874          $output = '';
1875  
1876          foreach ($this->flags as $flag) {
1877              if ($flag->is_enabled()) {
1878                  $output .= $flag->output_setting_flag($this);
1879              }
1880          }
1881  
1882          if (!empty($output)) {
1883              return html_writer::tag('span', $output, array('class' => 'adminsettingsflags'));
1884          }
1885          return $output;
1886      }
1887  
1888      /**
1889       * Write the values of the flags for this admin setting.
1890       *
1891       * @param array $data - The data submitted from the form or null to set the default value for new installs.
1892       * @return bool - true if successful.
1893       */
1894      public function write_setting_flags($data) {
1895          $result = true;
1896          foreach ($this->flags as $flag) {
1897              $result = $result && $flag->write_setting_flag($this, $data);
1898          }
1899          return $result;
1900      }
1901  
1902      /**
1903       * Set up $this->name and potentially $this->plugin
1904       *
1905       * Set up $this->name and possibly $this->plugin based on whether $name looks
1906       * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1907       * on the names, that is, output a developer debug warning if the name
1908       * contains anything other than [a-zA-Z0-9_]+.
1909       *
1910       * @param string $name the setting name passed in to the constructor.
1911       */
1912      private function parse_setting_name($name) {
1913          $bits = explode('/', $name);
1914          if (count($bits) > 2) {
1915              throw new moodle_exception('invalidadminsettingname', '', '', $name);
1916          }
1917          $this->name = array_pop($bits);
1918          if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1919              throw new moodle_exception('invalidadminsettingname', '', '', $name);
1920          }
1921          if (!empty($bits)) {
1922              $this->plugin = array_pop($bits);
1923              if ($this->plugin === 'moodle') {
1924                  $this->plugin = null;
1925              } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1926                      throw new moodle_exception('invalidadminsettingname', '', '', $name);
1927                  }
1928          }
1929      }
1930  
1931      /**
1932       * Returns the fullname prefixed by the plugin
1933       * @return string
1934       */
1935      public function get_full_name() {
1936          return 's_'.$this->plugin.'_'.$this->name;
1937      }
1938  
1939      /**
1940       * Returns the ID string based on plugin and name
1941       * @return string
1942       */
1943      public function get_id() {
1944          return 'id_s_'.$this->plugin.'_'.$this->name;
1945      }
1946  
1947      /**
1948       * @param bool $affectsmodinfo If true, changes to this setting will
1949       *   cause the course cache to be rebuilt
1950       */
1951      public function set_affects_modinfo($affectsmodinfo) {
1952          $this->affectsmodinfo = $affectsmodinfo;
1953      }
1954  
1955      /**
1956       * Returns the config if possible
1957       *
1958       * @return mixed returns config if successful else null
1959       */
1960      public function config_read($name) {
1961          global $CFG;
1962          if (!empty($this->plugin)) {
1963              $value = get_config($this->plugin, $name);
1964              return $value === false ? NULL : $value;
1965  
1966          } else {
1967              if (isset($CFG->$name)) {
1968                  return $CFG->$name;
1969              } else {
1970                  return NULL;
1971              }
1972          }
1973      }
1974  
1975      /**
1976       * Used to set a config pair and log change
1977       *
1978       * @param string $name
1979       * @param mixed $value Gets converted to string if not null
1980       * @return bool Write setting to config table
1981       */
1982      public function config_write($name, $value) {
1983          global $DB, $USER, $CFG;
1984  
1985          if ($this->nosave) {
1986              return true;
1987          }
1988  
1989          // make sure it is a real change
1990          $oldvalue = get_config($this->plugin, $name);
1991          $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1992          $value = is_null($value) ? null : (string)$value;
1993  
1994          if ($oldvalue === $value) {
1995              return true;
1996          }
1997  
1998          // store change
1999          set_config($name, $value, $this->plugin);
2000  
2001          // Some admin settings affect course modinfo
2002          if ($this->affectsmodinfo) {
2003              // Clear course cache for all courses
2004              rebuild_course_cache(0, true);
2005          }
2006  
2007          $this->add_to_config_log($name, $oldvalue, $value);
2008  
2009          return true; // BC only
2010      }
2011  
2012      /**
2013       * Log config changes if necessary.
2014       * @param string $name
2015       * @param string $oldvalue
2016       * @param string $value
2017       */
2018      protected function add_to_config_log($name, $oldvalue, $value) {
2019          add_to_config_log($name, $oldvalue, $value, $this->plugin);
2020      }
2021  
2022      /**
2023       * Returns current value of this setting
2024       * @return mixed array or string depending on instance, NULL means not set yet
2025       */
2026      public abstract function get_setting();
2027  
2028      /**
2029       * Returns default setting if exists
2030       * @return mixed array or string depending on instance; NULL means no default, user must supply
2031       */
2032      public function get_defaultsetting() {
2033          $adminroot =  admin_get_root(false, false);
2034          if (!empty($adminroot->custom_defaults)) {
2035              $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
2036              if (isset($adminroot->custom_defaults[$plugin])) {
2037                  if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
2038                      return $adminroot->custom_defaults[$plugin][$this->name];
2039                  }
2040              }
2041          }
2042          return $this->defaultsetting;
2043      }
2044  
2045      /**
2046       * Store new setting
2047       *
2048       * @param mixed $data string or array, must not be NULL
2049       * @return string empty string if ok, string error message otherwise
2050       */
2051      public abstract function write_setting($data);
2052  
2053      /**
2054       * Return part of form with setting
2055       * This function should always be overwritten
2056       *
2057       * @param mixed $data array or string depending on setting
2058       * @param string $query
2059       * @return string
2060       */
2061      public function output_html($data, $query='') {
2062      // should be overridden
2063          return;
2064      }
2065  
2066      /**
2067       * Function called if setting updated - cleanup, cache reset, etc.
2068       * @param string $functionname Sets the function name
2069       * @return void
2070       */
2071      public function set_updatedcallback($functionname) {
2072          $this->updatedcallback = $functionname;
2073      }
2074  
2075      /**
2076       * Execute postupdatecallback if necessary.
2077       * @param mixed $original original value before write_setting()
2078       * @return bool true if changed, false if not.
2079       */
2080      public function post_write_settings($original) {
2081          // Comparison must work for arrays too.
2082          if (serialize($original) === serialize($this->get_setting())) {
2083              return false;
2084          }
2085  
2086          $callbackfunction = $this->updatedcallback;
2087          if (!empty($callbackfunction) and is_callable($callbackfunction)) {
2088              $callbackfunction($this->get_full_name());
2089          }
2090          return true;
2091      }
2092  
2093      /**
2094       * Is setting related to query text - used when searching
2095       * @param string $query
2096       * @return bool
2097       */
2098      public function is_related($query) {
2099          if (strpos(strtolower($this->name), $query) !== false) {
2100              return true;
2101          }
2102          if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
2103              return true;
2104          }
2105          if (strpos(core_text::strtolower($this->description), $query) !== false) {
2106              return true;
2107          }
2108          $current = $this->get_setting();
2109          if (!is_null($current)) {
2110              if (is_string($current)) {
2111                  if (strpos(core_text::strtolower($current), $query) !== false) {
2112                      return true;
2113                  }
2114              }
2115          }
2116          $default = $this->get_defaultsetting();
2117          if (!is_null($default)) {
2118              if (is_string($default)) {
2119                  if (strpos(core_text::strtolower($default), $query) !== false) {
2120                      return true;
2121                  }
2122              }
2123          }
2124          return false;
2125      }
2126  
2127      /**
2128       * Get whether this should be displayed in LTR mode.
2129       *
2130       * @return bool|null
2131       */
2132      public function get_force_ltr() {
2133          return $this->forceltr;
2134      }
2135  
2136      /**
2137       * Set whether to force LTR or not.
2138       *
2139       * @param bool $value True when forced, false when not force, null when unknown.
2140       */
2141      public function set_force_ltr($value) {
2142          $this->forceltr = $value;
2143      }
2144  
2145      /**
2146       * Add a setting to the list of those that could cause this one to be hidden
2147       * @param string $dependenton
2148       */
2149      public function add_dependent_on($dependenton) {
2150          $this->dependenton[] = $dependenton;
2151      }
2152  
2153      /**
2154       * Get a list of the settings that could cause this one to be hidden.
2155       * @return array
2156       */
2157      public function get_dependent_on() {
2158          return $this->dependenton;
2159      }
2160  
2161      /**
2162       * Whether this setting uses a custom form control.
2163       * This function is especially useful to decide if we should render a label element for this setting or not.
2164       *
2165       * @return bool
2166       */
2167      public function has_custom_form_control(): bool {
2168          return $this->customcontrol;
2169      }
2170  
2171      /**
2172       * Whether the setting can be overridden in config.php.
2173       *
2174       * Returning true will allow the setting to be defined and overridden in config.php.
2175       * Returning false will prevent the config setting from being overridden even when it gets defined in config.php.
2176       *
2177       * @return bool
2178       */
2179      public function is_forceable(): bool {
2180          return true;
2181      }
2182  }
2183  
2184  /**
2185   * An additional option that can be applied to an admin setting.
2186   * The currently supported options are 'ADVANCED', 'LOCKED' and 'REQUIRED'.
2187   *
2188   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2189   */
2190  class admin_setting_flag {
2191      /** @var bool Flag to indicate if this option can be toggled for this setting */
2192      private $enabled = false;
2193      /** @var bool Flag to indicate if this option defaults to true or false */
2194      private $default = false;
2195      /** @var string Short string used to create setting name - e.g. 'adv' */
2196      private $shortname = '';
2197      /** @var string String used as the label for this flag */
2198      private $displayname = '';
2199      /** @var Checkbox for this flag is displayed in admin page */
2200      const ENABLED = true;
2201      /** @var Checkbox for this flag is not displayed in admin page */
2202      const DISABLED = false;
2203  
2204      /**
2205       * Constructor
2206       *
2207       * @param bool $enabled Can this option can be toggled.
2208       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2209       * @param bool $default The default checked state for this setting option.
2210       * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
2211       * @param string $displayname The displayname of this flag. Used as a label for the flag.
2212       */
2213      public function __construct($enabled, $default, $shortname, $displayname) {
2214          $this->shortname = $shortname;
2215          $this->displayname = $displayname;
2216          $this->set_options($enabled, $default);
2217      }
2218  
2219      /**
2220       * Update the values of this setting options class
2221       *
2222       * @param bool $enabled Can this option can be toggled.
2223       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2224       * @param bool $default The default checked state for this setting option.
2225       */
2226      public function set_options($enabled, $default) {
2227          $this->enabled = $enabled;
2228          $this->default = $default;
2229      }
2230  
2231      /**
2232       * Should this option appear in the interface and be toggleable?
2233       *
2234       * @return bool Is it enabled?
2235       */
2236      public function is_enabled() {
2237          return $this->enabled;
2238      }
2239  
2240      /**
2241       * Should this option be checked by default?
2242       *
2243       * @return bool Is it on by default?
2244       */
2245      public function get_default() {
2246          return $this->default;
2247      }
2248  
2249      /**
2250       * Return the short name for this flag. e.g. 'adv' or 'locked'
2251       *
2252       * @return string
2253       */
2254      public function get_shortname() {
2255          return $this->shortname;
2256      }
2257  
2258      /**
2259       * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
2260       *
2261       * @return string
2262       */
2263      public function get_displayname() {
2264          return $this->displayname;
2265      }
2266  
2267      /**
2268       * Save the submitted data for this flag - or set it to the default if $data is null.
2269       *
2270       * @param admin_setting $setting - The admin setting for this flag
2271       * @param array $data - The data submitted from the form or null to set the default value for new installs.
2272       * @return bool
2273       */
2274      public function write_setting_flag(admin_setting $setting, $data) {
2275          $result = true;
2276          if ($this->is_enabled()) {
2277              if (!isset($data)) {
2278                  $value = $this->get_default();
2279              } else {
2280                  $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
2281              }
2282              $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
2283          }
2284  
2285          return $result;
2286  
2287      }
2288  
2289      /**
2290       * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
2291       *
2292       * @param admin_setting $setting - The admin setting for this flag
2293       * @return string - The html for the checkbox.
2294       */
2295      public function output_setting_flag(admin_setting $setting) {
2296          global $OUTPUT;
2297  
2298          $value = $setting->get_setting_flag_value($this);
2299  
2300          $context = new stdClass();
2301          $context->id = $setting->get_id() . '_' . $this->get_shortname();
2302          $context->name = $setting->get_full_name() .  '_' . $this->get_shortname();
2303          $context->value = 1;
2304          $context->checked = $value ? true : false;
2305          $context->label = $this->get_displayname();
2306  
2307          return $OUTPUT->render_from_template('core_admin/setting_flag', $context);
2308      }
2309  }
2310  
2311  
2312  /**
2313   * No setting - just heading and text.
2314   *
2315   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2316   */
2317  class admin_setting_heading extends admin_setting {
2318  
2319      /**
2320       * not a setting, just text
2321       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2322       * @param string $heading heading
2323       * @param string $information text in box
2324       */
2325      public function __construct($name, $heading, $information) {
2326          $this->nosave = true;
2327          parent::__construct($name, $heading, $information, '');
2328      }
2329  
2330      /**
2331       * Always returns true
2332       * @return bool Always returns true
2333       */
2334      public function get_setting() {
2335          return true;
2336      }
2337  
2338      /**
2339       * Always returns true
2340       * @return bool Always returns true
2341       */
2342      public function get_defaultsetting() {
2343          return true;
2344      }
2345  
2346      /**
2347       * Never write settings
2348       * @return string Always returns an empty string
2349       */
2350      public function write_setting($data) {
2351      // do not write any setting
2352          return '';
2353      }
2354  
2355      /**
2356       * Returns an HTML string
2357       * @return string Returns an HTML string
2358       */
2359      public function output_html($data, $query='') {
2360          global $OUTPUT;
2361          $context = new stdClass();
2362          $context->title = $this->visiblename;
2363          $context->description = $this->description;
2364          $context->descriptionformatted = highlight($query, markdown_to_html($this->description));
2365          return $OUTPUT->render_from_template('core_admin/setting_heading', $context);
2366      }
2367  }
2368  
2369  /**
2370   * No setting - just name and description in same row.
2371   *
2372   * @copyright 2018 onwards Amaia Anabitarte
2373   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2374   */
2375  class admin_setting_description extends admin_setting {
2376  
2377      /**
2378       * Not a setting, just text
2379       *
2380       * @param string $name
2381       * @param string $visiblename
2382       * @param string $description
2383       */
2384      public function __construct($name, $visiblename, $description) {
2385          $this->nosave = true;
2386          parent::__construct($name, $visiblename, $description, '');
2387      }
2388  
2389      /**
2390       * Always returns true
2391       *
2392       * @return bool Always returns true
2393       */
2394      public function get_setting() {
2395          return true;
2396      }
2397  
2398      /**
2399       * Always returns true
2400       *
2401       * @return bool Always returns true
2402       */
2403      public function get_defaultsetting() {
2404          return true;
2405      }
2406  
2407      /**
2408       * Never write settings
2409       *
2410       * @param mixed $data Gets converted to str for comparison against yes value
2411       * @return string Always returns an empty string
2412       */
2413      public function write_setting($data) {
2414          // Do not write any setting.
2415          return '';
2416      }
2417  
2418      /**
2419       * Returns an HTML string
2420       *
2421       * @param string $data
2422       * @param string $query
2423       * @return string Returns an HTML string
2424       */
2425      public function output_html($data, $query='') {
2426          global $OUTPUT;
2427  
2428          $context = new stdClass();
2429          $context->title = $this->visiblename;
2430          $context->description = $this->description;
2431  
2432          return $OUTPUT->render_from_template('core_admin/setting_description', $context);
2433      }
2434  }
2435  
2436  
2437  
2438  /**
2439   * The most flexible setting, the user enters text.
2440   *
2441   * This type of field should be used for config settings which are using
2442   * English words and are not localised (passwords, database name, list of values, ...).
2443   *
2444   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2445   */
2446  class admin_setting_configtext extends admin_setting {
2447  
2448      /** @var int default field size */
2449      public $size;
2450  
2451      /**
2452       * Config text constructor
2453       *
2454       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2455       * @param string $visiblename localised
2456       * @param string $description long localised info
2457       * @param string $defaultsetting
2458       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2459       * @param int $size default field size
2460       */
2461      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2462          $this->paramtype = $paramtype;
2463          if (!is_null($size)) {
2464              $this->size  = $size;
2465          } else {
2466              $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
2467          }
2468          parent::__construct($name, $visiblename, $description, $defaultsetting);
2469      }
2470  
2471      /**
2472       * Get whether this should be displayed in LTR mode.
2473       *
2474       * Try to guess from the PARAM type unless specifically set.
2475       */
2476      public function get_force_ltr() {
2477          $forceltr = parent::get_force_ltr();
2478          if ($forceltr === null) {
2479              return !is_rtl_compatible($this->paramtype);
2480          }
2481          return $forceltr;
2482      }
2483  
2484      /**
2485       * Return the setting
2486       *
2487       * @return mixed returns config if successful else null
2488       */
2489      public function get_setting() {
2490          return $this->config_read($this->name);
2491      }
2492  
2493      public function write_setting($data) {
2494          if ($this->paramtype === PARAM_INT and $data === '') {
2495          // do not complain if '' used instead of 0
2496              $data = 0;
2497          }
2498          // $data is a string
2499          $validated = $this->validate($data);
2500          if ($validated !== true) {
2501              return $validated;
2502          }
2503          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2504      }
2505  
2506      /**
2507       * Validate data before storage
2508       * @param string data
2509       * @return mixed true if ok string if error found
2510       */
2511      public function validate($data) {
2512          // allow paramtype to be a custom regex if it is the form of /pattern/
2513          if (preg_match('#^/.*/$#', $this->paramtype)) {
2514              if (preg_match($this->paramtype, $data)) {
2515                  return true;
2516              } else {
2517                  return get_string('validateerror', 'admin');
2518              }
2519  
2520          } else if ($this->paramtype === PARAM_RAW) {
2521              return true;
2522  
2523          } else {
2524              $cleaned = clean_param($data, $this->paramtype);
2525              if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
2526                  return true;
2527              } else {
2528                  return get_string('validateerror', 'admin');
2529              }
2530          }
2531      }
2532  
2533      /**
2534       * Return an XHTML string for the setting
2535       * @return string Returns an XHTML string
2536       */
2537      public function output_html($data, $query='') {
2538          global $OUTPUT;
2539  
2540          $default = $this->get_defaultsetting();
2541          $context = (object) [
2542              'size' => $this->size,
2543              'id' => $this->get_id(),
2544              'name' => $this->get_full_name(),
2545              'value' => $data,
2546              'forceltr' => $this->get_force_ltr(),
2547              'readonly' => $this->is_readonly(),
2548          ];
2549          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
2550  
2551          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2552      }
2553  }
2554  
2555  /**
2556   * Text input with a maximum length constraint.
2557   *
2558   * @copyright 2015 onwards Ankit Agarwal
2559   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2560   */
2561  class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
2562  
2563      /** @var int maximum number of chars allowed. */
2564      protected $maxlength;
2565  
2566      /**
2567       * Config text constructor
2568       *
2569       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2570       *                     or 'myplugin/mysetting' for ones in config_plugins.
2571       * @param string $visiblename localised
2572       * @param string $description long localised info
2573       * @param string $defaultsetting
2574       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2575       * @param int $size default field size
2576       * @param mixed $maxlength int maxlength allowed, 0 for infinite.
2577       */
2578      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW,
2579                                  $size=null, $maxlength = 0) {
2580          $this->maxlength = $maxlength;
2581          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
2582      }
2583  
2584      /**
2585       * Validate data before storage
2586       *
2587       * @param string $data data
2588       * @return mixed true if ok string if error found
2589       */
2590      public function validate($data) {
2591          $parentvalidation = parent::validate($data);
2592          if ($parentvalidation === true) {
2593              if ($this->maxlength > 0) {
2594                  // Max length check.
2595                  $length = core_text::strlen($data);
2596                  if ($length > $this->maxlength) {
2597                      return get_string('maximumchars', 'moodle',  $this->maxlength);
2598                  }
2599                  return true;
2600              } else {
2601                  return true; // No max length check needed.
2602              }
2603          } else {
2604              return $parentvalidation;
2605          }
2606      }
2607  }
2608  
2609  /**
2610   * General text area without html editor.
2611   *
2612   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2613   */
2614  class admin_setting_configtextarea extends admin_setting_configtext {
2615      private $rows;
2616      private $cols;
2617  
2618      /**
2619       * @param string $name
2620       * @param string $visiblename
2621       * @param string $description
2622       * @param mixed $defaultsetting string or array
2623       * @param mixed $paramtype
2624       * @param string $cols The number of columns to make the editor
2625       * @param string $rows The number of rows to make the editor
2626       */
2627      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2628          $this->rows = $rows;
2629          $this->cols = $cols;
2630          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
2631      }
2632  
2633      /**
2634       * Returns an XHTML string for the editor
2635       *
2636       * @param string $data
2637       * @param string $query
2638       * @return string XHTML string for the editor
2639       */
2640      public function output_html($data, $query='') {
2641          global $OUTPUT;
2642  
2643          $default = $this->get_defaultsetting();
2644          $defaultinfo = $default;
2645          if (!is_null($default) and $default !== '') {
2646              $defaultinfo = "\n".$default;
2647          }
2648  
2649          $context = (object) [
2650              'cols' => $this->cols,
2651              'rows' => $this->rows,
2652              'id' => $this->get_id(),
2653              'name' => $this->get_full_name(),
2654              'value' => $data,
2655              'forceltr' => $this->get_force_ltr(),
2656              'readonly' => $this->is_readonly(),
2657          ];
2658          $element = $OUTPUT->render_from_template('core_admin/setting_configtextarea', $context);
2659  
2660          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
2661      }
2662  }
2663  
2664  /**
2665   * General text area with html editor.
2666   */
2667  class admin_setting_confightmleditor extends admin_setting_configtextarea {
2668  
2669      /**
2670       * @param string $name
2671       * @param string $visiblename
2672       * @param string $description
2673       * @param mixed $defaultsetting string or array
2674       * @param mixed $paramtype
2675       */
2676      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2677          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
2678          $this->set_force_ltr(false);
2679          editors_head_setup();
2680      }
2681  
2682      /**
2683       * Returns an XHTML string for the editor
2684       *
2685       * @param string $data
2686       * @param string $query
2687       * @return string XHTML string for the editor
2688       */
2689      public function output_html($data, $query='') {
2690          $editor = editors_get_preferred_editor(FORMAT_HTML);
2691          $editor->set_text($data);
2692          $editor->use_editor($this->get_id(), array('noclean'=>true));
2693          return parent::output_html($data, $query);
2694      }
2695  
2696      /**
2697       * Checks if data has empty html.
2698       *
2699       * @param string $data
2700       * @return string Empty when no errors.
2701       */
2702      public function write_setting($data) {
2703          if (trim(html_to_text($data)) === '') {
2704              $data = '';
2705          }
2706          return parent::write_setting($data);
2707      }
2708  }
2709  
2710  
2711  /**
2712   * Password field, allows unmasking of password
2713   *
2714   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2715   */
2716  class admin_setting_configpasswordunmask extends admin_setting_configtext {
2717  
2718      /**
2719       * Constructor
2720       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2721       * @param string $visiblename localised
2722       * @param string $description long localised info
2723       * @param string $defaultsetting default password
2724       */
2725      public function __construct($name, $visiblename, $description, $defaultsetting) {
2726          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2727      }
2728  
2729      /**
2730       * Log config changes if necessary.
2731       * @param string $name
2732       * @param string $oldvalue
2733       * @param string $value
2734       */
2735      protected function add_to_config_log($name, $oldvalue, $value) {
2736          if ($value !== '') {
2737              $value = '********';
2738          }
2739          if ($oldvalue !== '' and $oldvalue !== null) {
2740              $oldvalue = '********';
2741          }
2742          parent::add_to_config_log($name, $oldvalue, $value);
2743      }
2744  
2745      /**
2746       * Returns HTML for the field.
2747       *
2748       * @param   string  $data       Value for the field
2749       * @param   string  $query      Passed as final argument for format_admin_setting
2750       * @return  string              Rendered HTML
2751       */
2752      public function output_html($data, $query='') {
2753          global $OUTPUT;
2754  
2755          $context = (object) [
2756              'id' => $this->get_id(),
2757              'name' => $this->get_full_name(),
2758              'size' => $this->size,
2759              'value' => $this->is_readonly() ? null : $data,
2760              'forceltr' => $this->get_force_ltr(),
2761              'readonly' => $this->is_readonly(),
2762          ];
2763          $element = $OUTPUT->render_from_template('core_admin/setting_configpasswordunmask', $context);
2764          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', null, $query);
2765      }
2766  }
2767  
2768  /**
2769   * Password field, allows unmasking of password, with an advanced checkbox that controls an additional $name.'_adv' setting.
2770   *
2771   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2772   * @copyright 2018 Paul Holden (pholden@greenhead.ac.uk)
2773   */
2774  class admin_setting_configpasswordunmask_with_advanced extends admin_setting_configpasswordunmask {
2775  
2776      /**
2777       * Constructor
2778       *
2779       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2780       * @param string $visiblename localised
2781       * @param string $description long localised info
2782       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
2783       */
2784      public function __construct($name, $visiblename, $description, $defaultsetting) {
2785          parent::__construct($name, $visiblename, $description, $defaultsetting['value']);
2786          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
2787      }
2788  }
2789  
2790  /**
2791   * Admin setting class for encrypted values using secure encryption.
2792   *
2793   * @copyright 2019 The Open University
2794   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2795   */
2796  class admin_setting_encryptedpassword extends admin_setting {
2797  
2798      /**
2799       * Constructor. Same as parent except that the default value is always an empty string.
2800       *
2801       * @param string $name Internal name used in config table
2802       * @param string $visiblename Name shown on form
2803       * @param string $description Description that appears below field
2804       */
2805      public function __construct(string $name, string $visiblename, string $description) {
2806          parent::__construct($name, $visiblename, $description, '');
2807      }
2808  
2809      public function get_setting() {
2810          return $this->config_read($this->name);
2811      }
2812  
2813      public function write_setting($data) {
2814          $data = trim($data);
2815          if ($data === '') {
2816              // Value can really be set to nothing.
2817              $savedata = '';
2818          } else {
2819              // Encrypt value before saving it.
2820              $savedata = \core\encryption::encrypt($data);
2821          }
2822          return ($this->config_write($this->name, $savedata) ? '' : get_string('errorsetting', 'admin'));
2823      }
2824  
2825      public function output_html($data, $query='') {
2826          global $OUTPUT;
2827  
2828          $default = $this->get_defaultsetting();
2829          $context = (object) [
2830              'id' => $this->get_id(),
2831              'name' => $this->get_full_name(),
2832              'set' => $data !== '',
2833              'novalue' => $this->get_setting() === null
2834          ];
2835          $element = $OUTPUT->render_from_template('core_admin/setting_encryptedpassword', $context);
2836  
2837          return format_admin_setting($this, $this->visiblename, $element, $this->description,
2838                  true, '', $default, $query);
2839      }
2840  }
2841  
2842  /**
2843   * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
2844   * Note: Only advanced makes sense right now - locked does not.
2845   *
2846   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2847   */
2848  class admin_setting_configempty extends admin_setting_configtext {
2849  
2850      /**
2851       * @param string $name
2852       * @param string $visiblename
2853       * @param string $description
2854       */
2855      public function __construct($name, $visiblename, $description) {
2856          parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
2857      }
2858  
2859      /**
2860       * Returns an XHTML string for the hidden field
2861       *
2862       * @param string $data
2863       * @param string $query
2864       * @return string XHTML string for the editor
2865       */
2866      public function output_html($data, $query='') {
2867          global $OUTPUT;
2868  
2869          $context = (object) [
2870              'id' => $this->get_id(),
2871              'name' => $this->get_full_name()
2872          ];
2873          $element = $OUTPUT->render_from_template('core_admin/setting_configempty', $context);
2874  
2875          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', get_string('none'), $query);
2876      }
2877  }
2878  
2879  
2880  /**
2881   * Path to directory
2882   *
2883   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2884   */
2885  class admin_setting_configfile extends admin_setting_configtext {
2886      /**
2887       * Constructor
2888       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2889       * @param string $visiblename localised
2890       * @param string $description long localised info
2891       * @param string $defaultdirectory default directory location
2892       */
2893      public function __construct($name, $visiblename, $description, $defaultdirectory) {
2894          parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2895      }
2896  
2897      /**
2898       * Returns XHTML for the field
2899       *
2900       * Returns XHTML for the field and also checks whether the file
2901       * specified in $data exists using file_exists()
2902       *
2903       * @param string $data File name and path to use in value attr
2904       * @param string $query
2905       * @return string XHTML field
2906       */
2907      public function output_html($data, $query='') {
2908          global $CFG, $OUTPUT;
2909  
2910          $default = $this->get_defaultsetting();
2911          $context = (object) [
2912              'id' => $this->get_id(),
2913              'name' => $this->get_full_name(),
2914              'size' => $this->size,
2915              'value' => $data,
2916              'showvalidity' => !empty($data),
2917              'valid' => $data && file_exists($data),
2918              'readonly' => !empty($CFG->preventexecpath) || $this->is_readonly(),
2919              'forceltr' => $this->get_force_ltr(),
2920          ];
2921  
2922          if ($context->readonly) {
2923              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2924          }
2925  
2926          $element = $OUTPUT->render_from_template('core_admin/setting_configfile', $context);
2927  
2928          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2929      }
2930  
2931      /**
2932       * Checks if execpatch has been disabled in config.php
2933       */
2934      public function write_setting($data) {
2935          global $CFG;
2936          if (!empty($CFG->preventexecpath)) {
2937              if ($this->get_setting() === null) {
2938                  // Use default during installation.
2939                  $data = $this->get_defaultsetting();
2940                  if ($data === null) {
2941                      $data = '';
2942                  }
2943              } else {
2944                  return '';
2945              }
2946          }
2947          return parent::write_setting($data);
2948      }
2949  
2950  }
2951  
2952  
2953  /**
2954   * Path to executable file
2955   *
2956   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2957   */
2958  class admin_setting_configexecutable extends admin_setting_configfile {
2959  
2960      /**
2961       * Returns an XHTML field
2962       *
2963       * @param string $data This is the value for the field
2964       * @param string $query
2965       * @return string XHTML field
2966       */
2967      public function output_html($data, $query='') {
2968          global $CFG, $OUTPUT;
2969          $default = $this->get_defaultsetting();
2970          require_once("$CFG->libdir/filelib.php");
2971  
2972          $context = (object) [
2973              'id' => $this->get_id(),
2974              'name' => $this->get_full_name(),
2975              'size' => $this->size,
2976              'value' => $data,
2977              'showvalidity' => !empty($data),
2978              'valid' => $data && file_exists($data) && !is_dir($data) && file_is_executable($data),
2979              'readonly' => !empty($CFG->preventexecpath),
2980              'forceltr' => $this->get_force_ltr()
2981          ];
2982  
2983          if (!empty($CFG->preventexecpath)) {
2984              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2985          }
2986  
2987          $element = $OUTPUT->render_from_template('core_admin/setting_configexecutable', $context);
2988  
2989          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2990      }
2991  }
2992  
2993  
2994  /**
2995   * Path to directory
2996   *
2997   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2998   */
2999  class admin_setting_configdirectory extends admin_setting_configfile {
3000  
3001      /**
3002       * Returns an XHTML field
3003       *
3004       * @param string $data This is the value for the field
3005       * @param string $query
3006       * @return string XHTML
3007       */
3008      public function output_html($data, $query='') {
3009          global $CFG, $OUTPUT;
3010          $default = $this->get_defaultsetting();
3011  
3012          $context = (object) [
3013              'id' => $this->get_id(),
3014              'name' => $this->get_full_name(),
3015              'size' => $this->size,
3016              'value' => $data,
3017              'showvalidity' => !empty($data),
3018              'valid' => $data && file_exists($data) && is_dir($data),
3019              'readonly' => !empty($CFG->preventexecpath),
3020              'forceltr' => $this->get_force_ltr()
3021          ];
3022  
3023          if (!empty($CFG->preventexecpath)) {
3024              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
3025          }
3026  
3027          $element = $OUTPUT->render_from_template('core_admin/setting_configdirectory', $context);
3028  
3029          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
3030      }
3031  }
3032  
3033  
3034  /**
3035   * Checkbox
3036   *
3037   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3038   */
3039  class admin_setting_configcheckbox extends admin_setting {
3040      /** @var string Value used when checked */
3041      public $yes;
3042      /** @var string Value used when not checked */
3043      public $no;
3044  
3045      /**
3046       * Constructor
3047       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3048       * @param string $visiblename localised
3049       * @param string $description long localised info
3050       * @param string $defaultsetting
3051       * @param string $yes value used when checked
3052       * @param string $no value used when not checked
3053       */
3054      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
3055          parent::__construct($name, $visiblename, $description, $defaultsetting);
3056          $this->yes = (string)$yes;
3057          $this->no  = (string)$no;
3058      }
3059  
3060      /**
3061       * Retrieves the current setting using the objects name
3062       *
3063       * @return string
3064       */
3065      public function get_setting() {
3066          return $this->config_read($this->name);
3067      }
3068  
3069      /**
3070       * Sets the value for the setting
3071       *
3072       * Sets the value for the setting to either the yes or no values
3073       * of the object by comparing $data to yes
3074       *
3075       * @param mixed $data Gets converted to str for comparison against yes value
3076       * @return string empty string or error
3077       */
3078      public function write_setting($data) {
3079          if ((string)$data === $this->yes) { // convert to strings before comparison
3080              $data = $this->yes;
3081          } else {
3082              $data = $this->no;
3083          }
3084          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3085      }
3086  
3087      /**
3088       * Returns an XHTML checkbox field
3089       *
3090       * @param string $data If $data matches yes then checkbox is checked
3091       * @param string $query
3092       * @return string XHTML field
3093       */
3094      public function output_html($data, $query='') {
3095          global $OUTPUT;
3096  
3097          $context = (object) [
3098              'id' => $this->get_id(),
3099              'name' => $this->get_full_name(),
3100              'no' => $this->no,
3101              'value' => $this->yes,
3102              'checked' => (string) $data === $this->yes,
3103              'readonly' => $this->is_readonly(),
3104          ];
3105  
3106          $default = $this->get_defaultsetting();
3107          if (!is_null($default)) {
3108              if ((string)$default === $this->yes) {
3109                  $defaultinfo = get_string('checkboxyes', 'admin');
3110              } else {
3111                  $defaultinfo = get_string('checkboxno', 'admin');
3112              }
3113          } else {
3114              $defaultinfo = NULL;
3115          }
3116  
3117          $element = $OUTPUT->render_from_template('core_admin/setting_configcheckbox', $context);
3118  
3119          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3120      }
3121  }
3122  
3123  
3124  /**
3125   * Multiple checkboxes, each represents different value, stored in csv format
3126   *
3127   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3128   */
3129  class admin_setting_configmulticheckbox extends admin_setting {
3130      /** @var callable|null Loader function for choices */
3131      protected $choiceloader = null;
3132  
3133      /** @var array Array of choices value=>label. */
3134      public $choices;
3135  
3136      /**
3137       * Constructor: uses parent::__construct
3138       *
3139       * The $choices parameter may be either an array of $value => $label format,
3140       * e.g. [1 => get_string('yes')], or a callback function which takes no parameters and
3141       * returns an array in that format.
3142       *
3143       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3144       * @param string $visiblename localised
3145       * @param string $description long localised info
3146       * @param array $defaultsetting array of selected
3147       * @param array|callable $choices array of $value => $label for each checkbox, or a callback
3148       */
3149      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3150          if (is_array($choices)) {
3151              $this->choices = $choices;
3152          }
3153          if (is_callable($choices)) {
3154              $this->choiceloader = $choices;
3155          }
3156          parent::__construct($name, $visiblename, $description, $defaultsetting);
3157      }
3158  
3159      /**
3160       * This function may be used in ancestors for lazy loading of choices
3161       *
3162       * Override this method if loading of choices is expensive, such
3163       * as when it requires multiple db requests.
3164       *
3165       * @return bool true if loaded, false if error
3166       */
3167      public function load_choices() {
3168          if ($this->choiceloader) {
3169              if (!is_array($this->choices)) {
3170                  $this->choices = call_user_func($this->choiceloader);
3171              }
3172          }
3173          return true;
3174      }
3175  
3176      /**
3177       * Is setting related to query text - used when searching
3178       *
3179       * @param string $query
3180       * @return bool true on related, false on not or failure
3181       */
3182      public function is_related($query) {
3183          if (!$this->load_choices() or empty($this->choices)) {
3184              return false;
3185          }
3186          if (parent::is_related($query)) {
3187              return true;
3188          }
3189  
3190          foreach ($this->choices as $desc) {
3191              if (strpos(core_text::strtolower($desc), $query) !== false) {
3192                  return true;
3193              }
3194          }
3195          return false;
3196      }
3197  
3198      /**
3199       * Returns the current setting if it is set
3200       *
3201       * @return mixed null if null, else an array
3202       */
3203      public function get_setting() {
3204          $result = $this->config_read($this->name);
3205  
3206          if (is_null($result)) {
3207              return NULL;
3208          }
3209          if ($result === '') {
3210              return array();
3211          }
3212          $enabled = explode(',', $result);
3213          $setting = array();
3214          foreach ($enabled as $option) {
3215              $setting[$option] = 1;
3216          }
3217          return $setting;
3218      }
3219  
3220      /**
3221       * Saves the setting(s) provided in $data
3222       *
3223       * @param array $data An array of data, if not array returns empty str
3224       * @return mixed empty string on useless data or bool true=success, false=failed
3225       */
3226      public function write_setting($data) {
3227          if (!is_array($data)) {
3228              return ''; // ignore it
3229          }
3230          if (!$this->load_choices() or empty($this->choices)) {
3231              return '';
3232          }
3233          unset($data['xxxxx']);
3234          $result = array();
3235          foreach ($data as $key => $value) {
3236              if ($value and array_key_exists($key, $this->choices)) {
3237                  $result[] = $key;
3238              }
3239          }
3240          return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
3241      }
3242  
3243      /**
3244       * Returns XHTML field(s) as required by choices
3245       *
3246       * Relies on data being an array should data ever be another valid vartype with
3247       * acceptable value this may cause a warning/error
3248       * if (!is_array($data)) would fix the problem
3249       *
3250       * @todo Add vartype handling to ensure $data is an array
3251       *
3252       * @param array $data An array of checked values
3253       * @param string $query
3254       * @return string XHTML field
3255       */
3256      public function output_html($data, $query='') {
3257          global $OUTPUT;
3258  
3259          if (!$this->load_choices() or empty($this->choices)) {
3260              return '';
3261          }
3262  
3263          $default = $this->get_defaultsetting();
3264          if (is_null($default)) {
3265              $default = array();
3266          }
3267          if (is_null($data)) {
3268              $data = array();
3269          }
3270  
3271          $context = (object) [
3272              'id' => $this->get_id(),
3273              'name' => $this->get_full_name(),
3274              'readonly' => $this->is_readonly(),
3275          ];
3276  
3277          $options = array();
3278          $defaults = array();
3279          foreach ($this->choices as $key => $description) {
3280              if (!empty($default[$key])) {
3281                  $defaults[] = $description;
3282              }
3283  
3284              $options[] = [
3285                  'key' => $key,
3286                  'checked' => !empty($data[$key]),
3287                  'label' => highlightfast($query, $description)
3288              ];
3289          }
3290  
3291          if (is_null($default)) {
3292              $defaultinfo = null;
3293          } else if (!empty($defaults)) {
3294              $defaultinfo = implode(', ', $defaults);
3295          } else {
3296              $defaultinfo = get_string('none');
3297          }
3298  
3299          $context->options = $options;
3300          $context->hasoptions = !empty($options);
3301  
3302          $element = $OUTPUT->render_from_template('core_admin/setting_configmulticheckbox', $context);
3303  
3304          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', $defaultinfo, $query);
3305  
3306      }
3307  }
3308  
3309  
3310  /**
3311   * Multiple checkboxes 2, value stored as string 00101011
3312   *
3313   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3314   */
3315  class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
3316  
3317      /**
3318       * Returns the setting if set
3319       *
3320       * @return mixed null if not set, else an array of set settings
3321       */
3322      public function get_setting() {
3323          $result = $this->config_read($this->name);
3324          if (is_null($result)) {
3325              return NULL;
3326          }
3327          if (!$this->load_choices()) {
3328              return NULL;
3329          }
3330          $result = str_pad($result, count($this->choices), '0');
3331          $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
3332          $setting = array();
3333          foreach ($this->choices as $key=>$unused) {
3334              $value = array_shift($result);
3335              if ($value) {
3336                  $setting[$key] = 1;
3337              }
3338          }
3339          return $setting;
3340      }
3341  
3342      /**
3343       * Save setting(s) provided in $data param
3344       *
3345       * @param array $data An array of settings to save
3346       * @return mixed empty string for bad data or bool true=>success, false=>error
3347       */
3348      public function write_setting($data) {
3349          if (!is_array($data)) {
3350              return ''; // ignore it
3351          }
3352          if (!$this->load_choices() or empty($this->choices)) {
3353              return '';
3354          }
3355          $result = '';
3356          foreach ($this->choices as $key=>$unused) {
3357              if (!empty($data[$key])) {
3358                  $result .= '1';
3359              } else {
3360                  $result .= '0';
3361              }
3362          }
3363          return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
3364      }
3365  }
3366  
3367  
3368  /**
3369   * Select one value from list
3370   *
3371   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3372   */
3373  class admin_setting_configselect extends admin_setting {
3374      /** @var array Array of choices value=>label */
3375      public $choices;
3376      /** @var array Array of choices grouped using optgroups */
3377      public $optgroups;
3378      /** @var callable|null Loader function for choices */
3379      protected $choiceloader = null;
3380      /** @var callable|null Validation function */
3381      protected $validatefunction = null;
3382  
3383      /**
3384       * Constructor.
3385       *
3386       * If you want to lazy-load the choices, pass a callback function that returns a choice
3387       * array for the $choices parameter.
3388       *
3389       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3390       * @param string $visiblename localised
3391       * @param string $description long localised info
3392       * @param string|int $defaultsetting
3393       * @param array|callable|null $choices array of $value=>$label for each selection, or callback
3394       */
3395      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3396          // Look for optgroup and single options.
3397          if (is_array($choices)) {
3398              $this->choices = [];
3399              foreach ($choices as $key => $val) {
3400                  if (is_array($val)) {
3401                      $this->optgroups[$key] = $val;
3402                      $this->choices = array_merge($this->choices, $val);
3403                  } else {
3404                      $this->choices[$key] = $val;
3405                  }
3406              }
3407          }
3408          if (is_callable($choices)) {
3409              $this->choiceloader = $choices;
3410          }
3411  
3412          parent::__construct($name, $visiblename, $description, $defaultsetting);
3413      }
3414  
3415      /**
3416       * Sets a validate function.
3417       *
3418       * The callback will be passed one parameter, the new setting value, and should return either
3419       * an empty string '' if the value is OK, or an error message if not.
3420       *
3421       * @param callable|null $validatefunction Validate function or null to clear
3422       * @since Moodle 3.10
3423       */
3424      public function set_validate_function(?callable $validatefunction = null) {
3425          $this->validatefunction = $validatefunction;
3426      }
3427  
3428      /**
3429       * This function may be used in ancestors for lazy loading of choices
3430       *
3431       * Override this method if loading of choices is expensive, such
3432       * as when it requires multiple db requests.
3433       *
3434       * @return bool true if loaded, false if error
3435       */
3436      public function load_choices() {
3437          if ($this->choiceloader) {
3438              if (!is_array($this->choices)) {
3439                  $this->choices = call_user_func($this->choiceloader);
3440              }
3441              return true;
3442          }
3443          return true;
3444      }
3445  
3446      /**
3447       * Check if this is $query is related to a choice
3448       *
3449       * @param string $query
3450       * @return bool true if related, false if not
3451       */
3452      public function is_related($query) {
3453          if (parent::is_related($query)) {
3454              return true;
3455          }
3456          if (!$this->load_choices()) {
3457              return false;
3458          }
3459          foreach ($this->choices as $key=>$value) {
3460              if (strpos(core_text::strtolower($key), $query) !== false) {
3461                  return true;
3462              }
3463              if (strpos(core_text::strtolower($value), $query) !== false) {
3464                  return true;
3465              }
3466          }
3467          return false;
3468      }
3469  
3470      /**
3471       * Return the setting
3472       *
3473       * @return mixed returns config if successful else null
3474       */
3475      public function get_setting() {
3476          return $this->config_read($this->name);
3477      }
3478  
3479      /**
3480       * Save a setting
3481       *
3482       * @param string $data
3483       * @return string empty of error string
3484       */
3485      public function write_setting($data) {
3486          if (!$this->load_choices() or empty($this->choices)) {
3487              return '';
3488          }
3489          if (!array_key_exists($data, $this->choices)) {
3490              return ''; // ignore it
3491          }
3492  
3493          // Validate the new setting.
3494          $error = $this->validate_setting($data);
3495          if ($error) {
3496              return $error;
3497          }
3498  
3499          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3500      }
3501  
3502      /**
3503       * Validate the setting. This uses the callback function if provided; subclasses could override
3504       * to carry out validation directly in the class.
3505       *
3506       * @param string $data New value being set
3507       * @return string Empty string if valid, or error message text
3508       * @since Moodle 3.10
3509       */
3510      protected function validate_setting(string $data): string {
3511          // If validation function is specified, call it now.
3512          if ($this->validatefunction) {
3513              return call_user_func($this->validatefunction, $data);
3514          } else {
3515              return '';
3516          }
3517      }
3518  
3519      /**
3520       * Returns XHTML select field
3521       *
3522       * Ensure the options are loaded, and generate the XHTML for the select
3523       * element and any warning message. Separating this out from output_html
3524       * makes it easier to subclass this class.
3525       *
3526       * @param string $data the option to show as selected.
3527       * @param string $current the currently selected option in the database, null if none.
3528       * @param string $default the default selected option.
3529       * @return array the HTML for the select element, and a warning message.
3530       * @deprecated since Moodle 3.2
3531       */
3532      public function output_select_html($data, $current, $default, $extraname = '') {
3533          debugging('The method admin_setting_configselect::output_select_html is depreacted, do not use any more.', DEBUG_DEVELOPER);
3534      }
3535  
3536      /**
3537       * Returns XHTML select field and wrapping div(s)
3538       *
3539       * @see output_select_html()
3540       *
3541       * @param string $data the option to show as selected
3542       * @param string $query
3543       * @return string XHTML field and wrapping div
3544       */
3545      public function output_html($data, $query='') {
3546          global $OUTPUT;
3547  
3548          $default = $this->get_defaultsetting();
3549          $current = $this->get_setting();
3550  
3551          if (!$this->load_choices() || empty($this->choices)) {
3552              return '';
3553          }
3554  
3555          $context = (object) [
3556              'id' => $this->get_id(),
3557              'name' => $this->get_full_name(),
3558          ];
3559  
3560          if (!is_null($default) && array_key_exists($default, $this->choices)) {
3561              $defaultinfo = $this->choices[$default];
3562          } else {
3563              $defaultinfo = NULL;
3564          }
3565  
3566          // Warnings.
3567          $warning = '';
3568          if ($current === null) {
3569              // First run.
3570          } else if (empty($current) && (array_key_exists('', $this->choices) || array_key_exists(0, $this->choices))) {
3571              // No warning.
3572          } else if (!array_key_exists($current, $this->choices)) {
3573              $warning = get_string('warningcurrentsetting', 'admin', $current);
3574              if (!is_null($default) && $data == $current) {
3575                  $data = $default; // Use default instead of first value when showing the form.
3576              }
3577          }
3578  
3579          $options = [];
3580          $template = 'core_admin/setting_configselect';
3581  
3582          if (!empty($this->optgroups)) {
3583              $optgroups = [];
3584              foreach ($this->optgroups as $label => $choices) {
3585                  $optgroup = array('label' => $label, 'options' => []);
3586                  foreach ($choices as $value => $name) {
3587                      $optgroup['options'][] = [
3588                          'value' => $value,
3589                          'name' => $name,
3590                          'selected' => (string) $value == $data
3591                      ];
3592                      unset($this->choices[$value]);
3593                  }
3594                  $optgroups[] = $optgroup;
3595              }
3596              $context->options = $options;
3597              $context->optgroups = $optgroups;
3598              $template = 'core_admin/setting_configselect_optgroup';
3599          }
3600  
3601          foreach ($this->choices as $value => $name) {
3602              $options[] = [
3603                  'value' => $value,
3604                  'name' => $name,
3605                  'selected' => (string) $value == $data
3606              ];
3607          }
3608          $context->options = $options;
3609          $context->readonly = $this->is_readonly();
3610  
3611          $element = $OUTPUT->render_from_template($template, $context);
3612  
3613          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, $warning, $defaultinfo, $query);
3614      }
3615  }
3616  
3617  /**
3618   * Select multiple items from list
3619   *
3620   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3621   */
3622  class admin_setting_configmultiselect extends admin_setting_configselect {
3623      /**
3624       * Constructor
3625       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3626       * @param string $visiblename localised
3627       * @param string $description long localised info
3628       * @param array $defaultsetting array of selected items
3629       * @param array $choices array of $value=>$label for each list item
3630       */
3631      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3632          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
3633      }
3634  
3635      /**
3636       * Returns the select setting(s)
3637       *
3638       * @return mixed null or array. Null if no settings else array of setting(s)
3639       */
3640      public function get_setting() {
3641          $result = $this->config_read($this->name);
3642          if (is_null($result)) {
3643              return NULL;
3644          }
3645          if ($result === '') {
3646              return array();
3647          }
3648          return explode(',', $result);
3649      }
3650  
3651      /**
3652       * Saves setting(s) provided through $data
3653       *
3654       * Potential bug in the works should anyone call with this function
3655       * using a vartype that is not an array
3656       *
3657       * @param array $data
3658       */
3659      public function write_setting($data) {
3660          if (!is_array($data)) {
3661              return ''; //ignore it
3662          }
3663          if (!$this->load_choices() or empty($this->choices)) {
3664              return '';
3665          }
3666  
3667          unset($data['xxxxx']);
3668  
3669          $save = array();
3670          foreach ($data as $value) {
3671              if (!array_key_exists($value, $this->choices)) {
3672                  continue; // ignore it
3673              }
3674              $save[] = $value;
3675          }
3676  
3677          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3678      }
3679  
3680      /**
3681       * Is setting related to query text - used when searching
3682       *
3683       * @param string $query
3684       * @return bool true if related, false if not
3685       */
3686      public function is_related($query) {
3687          if (!$this->load_choices() or empty($this->choices)) {
3688              return false;
3689          }
3690          if (parent::is_related($query)) {
3691              return true;
3692          }
3693  
3694          foreach ($this->choices as $desc) {
3695              if (strpos(core_text::strtolower($desc), $query) !== false) {
3696                  return true;
3697              }
3698          }
3699          return false;
3700      }
3701  
3702      /**
3703       * Returns XHTML multi-select field
3704       *
3705       * @todo Add vartype handling to ensure $data is an array
3706       * @param array $data Array of values to select by default
3707       * @param string $query
3708       * @return string XHTML multi-select field
3709       */
3710      public function output_html($data, $query='') {
3711          global $OUTPUT;
3712  
3713          if (!$this->load_choices() or empty($this->choices)) {
3714              return '';
3715          }
3716  
3717          $default = $this->get_defaultsetting();
3718          if (is_null($default)) {
3719              $default = array();
3720          }
3721          if (is_null($data)) {
3722              $data = array();
3723          }
3724  
3725          $context = (object) [
3726              'id' => $this->get_id(),
3727              'name' => $this->get_full_name(),
3728              'size' => min(10, count($this->choices))
3729          ];
3730  
3731          $defaults = [];
3732          $options = [];
3733          $template = 'core_admin/setting_configmultiselect';
3734  
3735          if (!empty($this->optgroups)) {
3736              $optgroups = [];
3737              foreach ($this->optgroups as $label => $choices) {
3738                  $optgroup = array('label' => $label, 'options' => []);
3739                  foreach ($choices as $value => $name) {
3740                      if (in_array($value, $default)) {
3741                          $defaults[] = $name;
3742                      }
3743                      $optgroup['options'][] = [
3744                          'value' => $value,
3745                          'name' => $name,
3746                          'selected' => in_array($value, $data)
3747                      ];
3748                      unset($this->choices[$value]);
3749                  }
3750                  $optgroups[] = $optgroup;
3751              }
3752              $context->optgroups = $optgroups;
3753              $template = 'core_admin/setting_configmultiselect_optgroup';
3754          }
3755  
3756          foreach ($this->choices as $value => $name) {
3757              if (in_array($value, $default)) {
3758                  $defaults[] = $name;
3759              }
3760              $options[] = [
3761                  'value' => $value,
3762                  'name' => $name,
3763                  'selected' => in_array($value, $data)
3764              ];
3765          }
3766          $context->options = $options;
3767          $context->readonly = $this->is_readonly();
3768  
3769          if (is_null($default)) {
3770              $defaultinfo = NULL;
3771          } if (!empty($defaults)) {
3772              $defaultinfo = implode(', ', $defaults);
3773          } else {
3774              $defaultinfo = get_string('none');
3775          }
3776  
3777          $element = $OUTPUT->render_from_template($template, $context);
3778  
3779          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3780      }
3781  }
3782  
3783  /**
3784   * Time selector
3785   *
3786   * This is a liiitle bit messy. we're using two selects, but we're returning
3787   * them as an array named after $name (so we only use $name2 internally for the setting)
3788   *
3789   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3790   */
3791  class admin_setting_configtime extends admin_setting {
3792      /** @var string Used for setting second select (minutes) */
3793      public $name2;
3794  
3795      /**
3796       * Constructor
3797       * @param string $hoursname setting for hours
3798       * @param string $minutesname setting for hours
3799       * @param string $visiblename localised
3800       * @param string $description long localised info
3801       * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3802       */
3803      public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
3804          $this->name2 = $minutesname;
3805          parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
3806      }
3807  
3808      /**
3809       * Get the selected time
3810       *
3811       * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
3812       */
3813      public function get_setting() {
3814          $result1 = $this->config_read($this->name);
3815          $result2 = $this->config_read($this->name2);
3816          if (is_null($result1) or is_null($result2)) {
3817              return NULL;
3818          }
3819  
3820          return array('h' => $result1, 'm' => $result2);
3821      }
3822  
3823      /**
3824       * Store the time (hours and minutes)
3825       *
3826       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3827       * @return bool true if success, false if not
3828       */
3829      public function write_setting($data) {
3830          if (!is_array($data)) {
3831              return '';
3832          }
3833  
3834          $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3835          return ($result ? '' : get_string('errorsetting', 'admin'));
3836      }
3837  
3838      /**
3839       * Returns XHTML time select fields
3840       *
3841       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3842       * @param string $query
3843       * @return string XHTML time select fields and wrapping div(s)
3844       */
3845      public function output_html($data, $query='') {
3846          global $OUTPUT;
3847  
3848          $default = $this->get_defaultsetting();
3849          if (is_array($default)) {
3850              $defaultinfo = $default['h'].':'.$default['m'];
3851          } else {
3852              $defaultinfo = NULL;
3853          }
3854  
3855          $context = (object) [
3856              'id' => $this->get_id(),
3857              'name' => $this->get_full_name(),
3858              'readonly' => $this->is_readonly(),
3859              'hours' => array_map(function($i) use ($data) {
3860                  return [
3861                      'value' => $i,
3862                      'name' => $i,
3863                      'selected' => $i == $data['h']
3864                  ];
3865              }, range(0, 23)),
3866              'minutes' => array_map(function($i) use ($data) {
3867                  return [
3868                      'value' => $i,
3869                      'name' => $i,
3870                      'selected' => $i == $data['m']
3871                  ];
3872              }, range(0, 59, 5))
3873          ];
3874  
3875          $element = $OUTPUT->render_from_template('core_admin/setting_configtime', $context);
3876  
3877          return format_admin_setting($this, $this->visiblename, $element, $this->description,
3878              $this->get_id() . 'h', '', $defaultinfo, $query);
3879      }
3880  
3881  }
3882  
3883  
3884  /**
3885   * Seconds duration setting.
3886   *
3887   * @copyright 2012 Petr Skoda (http://skodak.org)
3888   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3889   */
3890  class admin_setting_configduration extends admin_setting {
3891  
3892      /** @var int default duration unit */
3893      protected $defaultunit;
3894      /** @var callable|null Validation function */
3895      protected $validatefunction = null;
3896  
3897      /** @var int The minimum allowed value */
3898      protected int $minduration = 0;
3899  
3900      /** @var null|int The maximum allowed value */
3901      protected null|int $maxduration = null;
3902  
3903      /**
3904       * Constructor
3905       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3906       *                     or 'myplugin/mysetting' for ones in config_plugins.
3907       * @param string $visiblename localised name
3908       * @param string $description localised long description
3909       * @param mixed $defaultsetting string or array depending on implementation
3910       * @param int $defaultunit - day, week, etc. (in seconds)
3911       */
3912      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3913          if (is_number($defaultsetting)) {
3914              $defaultsetting = self::parse_seconds($defaultsetting);
3915          }
3916          $units = self::get_units();
3917          if (isset($units[$defaultunit])) {
3918              $this->defaultunit = $defaultunit;
3919          } else {
3920              $this->defaultunit = 86400;
3921          }
3922          parent::__construct($name, $visiblename, $description, $defaultsetting);
3923      }
3924  
3925      /**
3926       * Set the minimum allowed value.
3927       * This must be at least 0.
3928       *
3929       * @param int $duration
3930       */
3931      public function set_min_duration(int $duration): void {
3932          if ($duration < 0) {
3933              throw new coding_exception('The minimum duration must be at least 0.');
3934          }
3935  
3936          $this->minduration = $duration;
3937      }
3938  
3939      /**
3940       * Set the maximum allowed value.
3941       *
3942       * A value of null will disable the maximum duration value.
3943       *
3944       * @param int|null $duration
3945       */
3946      public function set_max_duration(?int $duration): void {
3947          $this->maxduration = $duration;
3948      }
3949  
3950      /**
3951       * Sets a validate function.
3952       *
3953       * The callback will be passed one parameter, the new setting value, and should return either
3954       * an empty string '' if the value is OK, or an error message if not.
3955       *
3956       * @param callable|null $validatefunction Validate function or null to clear
3957       * @since Moodle 3.10
3958       */
3959      public function set_validate_function(?callable $validatefunction = null) {
3960          $this->validatefunction = $validatefunction;
3961      }
3962  
3963      /**
3964       * Validate the setting. This uses the callback function if provided; subclasses could override
3965       * to carry out validation directly in the class.
3966       *
3967       * @param int $data New value being set
3968       * @return string Empty string if valid, or error message text
3969       * @since Moodle 3.10
3970       */
3971      protected function validate_setting(int $data): string {
3972          if ($data < $this->minduration) {
3973              return get_string(
3974                  'configduration_low',
3975                  'admin',
3976                  self::get_duration_text($this->minduration, get_string('numseconds', 'core', 0))
3977              );
3978          }
3979  
3980          if ($this->maxduration && $data > $this->maxduration) {
3981              return get_string('configduration_high', 'admin', self::get_duration_text($this->maxduration));
3982          }
3983  
3984          // If validation function is specified, call it now.
3985          if ($this->validatefunction) {
3986              return call_user_func($this->validatefunction, $data);
3987          }
3988          return '';
3989      }
3990  
3991      /**
3992       * Returns selectable units.
3993       * @static
3994       * @return array
3995       */
3996      protected static function get_units() {
3997          return array(
3998              604800 => get_string('weeks'),
3999              86400 => get_string('days'),
4000              3600 => get_string('hours'),
4001              60 => get_string('minutes'),
4002              1 => get_string('seconds'),
4003          );
4004      }
4005  
4006      /**
4007       * Converts seconds to some more user friendly string.
4008       * @static
4009       * @param int $seconds
4010       * @param null|string The value to use when the duration is empty. If not specified, a "None" value is used.
4011       * @return string
4012       */
4013      protected static function get_duration_text(int $seconds, ?string $emptyvalue = null): string {
4014          if (empty($seconds)) {
4015              if ($emptyvalue !== null) {
4016                  return $emptyvalue;
4017              }
4018              return get_string('none');
4019          }
4020          $data = self::parse_seconds($seconds);
4021          switch ($data['u']) {
4022              case (60*60*24*7):
4023                  return get_string('numweeks', '', $data['v']);
4024              case (60*60*24):
4025                  return get_string('numdays', '', $data['v']);
4026              case (60*60):
4027                  return get_string('numhours', '', $data['v']);
4028              case (60):
4029                  return get_string('numminutes', '', $data['v']);
4030              default:
4031                  return get_string('numseconds', '', $data['v']*$data['u']);
4032          }
4033      }
4034  
4035      /**
4036       * Finds suitable units for given duration.
4037       * @static
4038       * @param int $seconds
4039       * @return array
4040       */
4041      protected static function parse_seconds($seconds) {
4042          foreach (self::get_units() as $unit => $unused) {
4043              if ($seconds % $unit === 0) {
4044                  return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
4045              }
4046          }
4047          return array('v'=>(int)$seconds, 'u'=>1);
4048      }
4049  
4050      /**
4051       * Get the selected duration as array.
4052       *
4053       * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
4054       */
4055      public function get_setting() {
4056          $seconds = $this->config_read($this->name);
4057          if (is_null($seconds)) {
4058              return null;
4059          }
4060  
4061          return self::parse_seconds($seconds);
4062      }
4063  
4064      /**
4065       * Store the duration as seconds.
4066       *
4067       * @param array $data Must be form 'h'=>xx, 'm'=>xx
4068       * @return bool true if success, false if not
4069       */
4070      public function write_setting($data) {
4071          if (!is_array($data)) {
4072              return '';
4073          }
4074  
4075          $unit = (int)$data['u'];
4076          $value = (int)$data['v'];
4077          $seconds = $value * $unit;
4078  
4079          // Validate the new setting.
4080          $error = $this->validate_setting($seconds);
4081          if ($error) {
4082              return $error;
4083          }
4084  
4085          $result = $this->config_write($this->name, $seconds);
4086          return ($result ? '' : get_string('errorsetting', 'admin'));
4087      }
4088  
4089      /**
4090       * Returns duration text+select fields.
4091       *
4092       * @param array $data Must be form 'v'=>xx, 'u'=>xx
4093       * @param string $query
4094       * @return string duration text+select fields and wrapping div(s)
4095       */
4096      public function output_html($data, $query='') {
4097          global $OUTPUT;
4098  
4099          $default = $this->get_defaultsetting();
4100          if (is_number($default)) {
4101              $defaultinfo = self::get_duration_text($default);
4102          } else if (is_array($default)) {
4103              $defaultinfo = self::get_duration_text($default['v']*$default['u']);
4104          } else {
4105              $defaultinfo = null;
4106          }
4107  
4108          $inputid = $this->get_id() . 'v';
4109          $units = array_filter(self::get_units(), function($unit): bool {
4110              if (!$this->maxduration) {
4111                  // No duration limit. All units are valid.
4112                  return true;
4113              }
4114  
4115              return $unit <= $this->maxduration;
4116          }, ARRAY_FILTER_USE_KEY);
4117  
4118          $defaultunit = $this->defaultunit;
4119  
4120          $context = (object) [
4121              'id' => $this->get_id(),
4122              'name' => $this->get_full_name(),
4123              'value' => $data['v'] ?? '',
4124              'readonly' => $this->is_readonly(),
4125              'options' => array_map(function($unit) use ($units, $data, $defaultunit) {
4126                  return [
4127                      'value' => $unit,
4128                      'name' => $units[$unit],
4129                      'selected' => isset($data) && (($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u'])
4130                  ];
4131              }, array_keys($units))
4132          ];
4133  
4134          $element = $OUTPUT->render_from_template('core_admin/setting_configduration', $context);
4135  
4136          return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
4137      }
4138  }
4139  
4140  
4141  /**
4142   * Seconds duration setting with an advanced checkbox, that controls a additional
4143   * $name.'_adv' setting.
4144   *
4145   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4146   * @copyright 2014 The Open University
4147   */
4148  class admin_setting_configduration_with_advanced extends admin_setting_configduration {
4149      /**
4150       * Constructor
4151       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
4152       *                     or 'myplugin/mysetting' for ones in config_plugins.
4153       * @param string $visiblename localised name
4154       * @param string $description localised long description
4155       * @param array  $defaultsetting array of int value, and bool whether it is
4156       *                     is advanced by default.
4157       * @param int $defaultunit - day, week, etc. (in seconds)
4158       */
4159      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
4160          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $defaultunit);
4161          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
4162      }
4163  }
4164  
4165  
4166  /**
4167   * Used to validate a textarea used for ip addresses
4168   *
4169   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4170   * @copyright 2011 Petr Skoda (http://skodak.org)
4171   */
4172  class admin_setting_configiplist extends admin_setting_configtextarea {
4173  
4174      /**
4175       * Validate the contents of the textarea as IP addresses
4176       *
4177       * Used to validate a new line separated list of IP addresses collected from
4178       * a textarea control
4179       *
4180       * @param string $data A list of IP Addresses separated by new lines
4181       * @return mixed bool true for success or string:error on failure
4182       */
4183      public function validate($data) {
4184          if(!empty($data)) {
4185              $lines = explode("\n", $data);
4186          } else {
4187              return true;
4188          }
4189          $result = true;
4190          $badips = array();
4191          foreach ($lines as $line) {
4192              $tokens = explode('#', $line);
4193              $ip = trim($tokens[0]);
4194              if (empty($ip)) {
4195                  continue;
4196              }
4197              if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
4198                  preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
4199                  preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
4200              } else {
4201                  $result = false;
4202                  $badips[] = $ip;
4203              }
4204          }
4205          if($result) {
4206              return true;
4207          } else {
4208              return get_string('validateiperror', 'admin', join(', ', $badips));
4209          }
4210      }
4211  }
4212  
4213  /**
4214   * Used to validate a textarea used for domain names, wildcard domain names and IP addresses/ranges (both IPv4 and IPv6 format).
4215   *
4216   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4217   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4218   */
4219  class admin_setting_configmixedhostiplist extends admin_setting_configtextarea {
4220  
4221      /**
4222       * Validate the contents of the textarea as either IP addresses, domain name or wildcard domain name (RFC 4592).
4223       * Used to validate a new line separated list of entries collected from a textarea control.
4224       *
4225       * This setting provides support for internationalised domain names (IDNs), however, such UTF-8 names will be converted to
4226       * their ascii-compatible encoding (punycode) on save, and converted back to their UTF-8 representation when fetched
4227       * via the get_setting() method, which has been overriden.
4228       *
4229       * @param string $data A list of FQDNs, DNS wildcard format domains, and IP addresses, separated by new lines.
4230       * @return mixed bool true for success or string:error on failure
4231       */
4232      public function validate($data) {
4233          if (empty($data)) {
4234              return true;
4235          }
4236          $entries = explode("\n", $data);
4237          $badentries = [];
4238  
4239          foreach ($entries as $key => $entry) {
4240              $entry = trim($entry);
4241              if (empty($entry)) {
4242                  return get_string('validateemptylineerror', 'admin');
4243              }
4244  
4245              // Validate each string entry against the supported formats.
4246              if (\core\ip_utils::is_ip_address($entry) || \core\ip_utils::is_ipv6_range($entry)
4247                      || \core\ip_utils::is_ipv4_range($entry) || \core\ip_utils::is_domain_name($entry)
4248                      || \core\ip_utils::is_domain_matching_pattern($entry)) {
4249                  continue;
4250              }
4251  
4252              // Otherwise, the entry is invalid.
4253              $badentries[] = $entry;
4254          }
4255  
4256          if ($badentries) {
4257              return get_string('validateerrorlist', 'admin', join(', ', $badentries));
4258          }
4259          return true;
4260      }
4261  
4262      /**
4263       * Convert any lines containing international domain names (IDNs) to their ascii-compatible encoding (ACE).
4264       *
4265       * @param string $data the setting data, as sent from the web form.
4266       * @return string $data the setting data, with all IDNs converted (using punycode) to their ascii encoded version.
4267       */
4268      protected function ace_encode($data) {
4269          if (empty($data)) {
4270              return $data;
4271          }
4272          $entries = explode("\n", $data);
4273          foreach ($entries as $key => $entry) {
4274              $entry = trim($entry);
4275              // This regex matches any string that has non-ascii character.
4276              if (preg_match('/[^\x00-\x7f]/', $entry)) {
4277                  // If we can convert the unicode string to an idn, do so.
4278                  // Otherwise, leave the original unicode string alone and let the validation function handle it (it will fail).
4279                  $val = idn_to_ascii($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4280                  $entries[$key] = $val ? $val : $entry;
4281              }
4282          }
4283          return implode("\n", $entries);
4284      }
4285  
4286      /**
4287       * Decode any ascii-encoded domain names back to their utf-8 representation for display.
4288       *
4289       * @param string $data the setting data, as found in the database.
4290       * @return string $data the setting data, with all ascii-encoded IDNs decoded back to their utf-8 representation.
4291       */
4292      protected function ace_decode($data) {
4293          $entries = explode("\n", $data);
4294          foreach ($entries as $key => $entry) {
4295              $entry = trim($entry);
4296              if (strpos($entry, 'xn--') !== false) {
4297                  $entries[$key] = idn_to_utf8($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4298              }
4299          }
4300          return implode("\n", $entries);
4301      }
4302  
4303      /**
4304       * Override, providing utf8-decoding for ascii-encoded IDN strings.
4305       *
4306       * @return mixed returns punycode-converted setting string if successful, else null.
4307       */
4308      public function get_setting() {
4309          // Here, we need to decode any ascii-encoded IDNs back to their native, utf-8 representation.
4310          $data = $this->config_read($this->name);
4311          if (function_exists('idn_to_utf8') && !is_null($data)) {
4312              $data = $this->ace_decode($data);
4313          }
4314          return $data;
4315      }
4316  
4317      /**
4318       * Override, providing ascii-encoding for utf8 (native) IDN strings.
4319       *
4320       * @param string $data
4321       * @return string
4322       */
4323      public function write_setting($data) {
4324          if ($this->paramtype === PARAM_INT and $data === '') {
4325              // Do not complain if '' used instead of 0.
4326              $data = 0;
4327          }
4328  
4329          // Try to convert any non-ascii domains to ACE prior to validation - we can't modify anything in validate!
4330          if (function_exists('idn_to_ascii')) {
4331              $data = $this->ace_encode($data);
4332          }
4333  
4334          $validated = $this->validate($data);
4335          if ($validated !== true) {
4336              return $validated;
4337          }
4338          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
4339      }
4340  }
4341  
4342  /**
4343   * Used to validate a textarea used for port numbers.
4344   *
4345   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4346   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4347   */
4348  class admin_setting_configportlist extends admin_setting_configtextarea {
4349  
4350      /**
4351       * Validate the contents of the textarea as port numbers.
4352       * Used to validate a new line separated list of ports collected from a textarea control.
4353       *
4354       * @param string $data A list of ports separated by new lines
4355       * @return mixed bool true for success or string:error on failure
4356       */
4357      public function validate($data) {
4358          if (empty($data)) {
4359              return true;
4360          }
4361          $ports = explode("\n", $data);
4362          $badentries = [];
4363          foreach ($ports as $port) {
4364              $port = trim($port);
4365              if (empty($port)) {
4366                  return get_string('validateemptylineerror', 'admin');
4367              }
4368  
4369              // Is the string a valid integer number?
4370              if (strval(intval($port)) !== $port || intval($port) <= 0) {
4371                  $badentries[] = $port;
4372              }
4373          }
4374          if ($badentries) {
4375              return get_string('validateerrorlist', 'admin', $badentries);
4376          }
4377          return true;
4378      }
4379  }
4380  
4381  
4382  /**
4383   * An admin setting for selecting one or more users who have a capability
4384   * in the system context
4385   *
4386   * An admin setting for selecting one or more users, who have a particular capability
4387   * in the system context. Warning, make sure the list will never be too long. There is
4388   * no paging or searching of this list.
4389   *
4390   * To correctly get a list of users from this config setting, you need to call the
4391   * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
4392   *
4393   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4394   */
4395  class admin_setting_users_with_capability extends admin_setting_configmultiselect {
4396      /** @var string The capabilities name */
4397      protected $capability;
4398      /** @var int include admin users too */
4399      protected $includeadmins;
4400  
4401      /**
4402       * Constructor.
4403       *
4404       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
4405       * @param string $visiblename localised name
4406       * @param string $description localised long description
4407       * @param array $defaultsetting array of usernames
4408       * @param string $capability string capability name.
4409       * @param bool $includeadmins include administrators
4410       */
4411      function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
4412          $this->capability    = $capability;
4413          $this->includeadmins = $includeadmins;
4414          parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
4415      }
4416  
4417      /**
4418       * Load all of the uses who have the capability into choice array
4419       *
4420       * @return bool Always returns true
4421       */
4422      function load_choices() {
4423          if (is_array($this->choices)) {
4424              return true;
4425          }
4426          list($sort, $sortparams) = users_order_by_sql('u');
4427          if (!empty($sortparams)) {
4428              throw new coding_exception('users_order_by_sql returned some query parameters. ' .
4429                      'This is unexpected, and a problem because there is no way to pass these ' .
4430                      'parameters to get_users_by_capability. See MDL-34657.');
4431          }
4432          $userfieldsapi = \core_user\fields::for_name();
4433          $userfields = 'u.id, u.username, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects;
4434          $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort);
4435          $this->choices = array(
4436              '$@NONE@$' => get_string('nobody'),
4437              '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
4438          );
4439          if ($this->includeadmins) {
4440              $admins = get_admins();
4441              foreach ($admins as $user) {
4442                  $this->choices[$user->id] = fullname($user);
4443              }
4444          }
4445          if (is_array($users)) {
4446              foreach ($users as $user) {
4447                  $this->choices[$user->id] = fullname($user);
4448              }
4449          }
4450          return true;
4451      }
4452  
4453      /**
4454       * Returns the default setting for class
4455       *
4456       * @return mixed Array, or string. Empty string if no default
4457       */
4458      public function get_defaultsetting() {
4459          $this->load_choices();
4460          $defaultsetting = parent::get_defaultsetting();
4461          if (empty($defaultsetting)) {
4462              return array('$@NONE@$');
4463          } else if (array_key_exists($defaultsetting, $this->choices)) {
4464                  return $defaultsetting;
4465              } else {
4466                  return '';
4467              }
4468      }
4469  
4470      /**
4471       * Returns the current setting
4472       *
4473       * @return mixed array or string
4474       */
4475      public function get_setting() {
4476          $result = parent::get_setting();
4477          if ($result === null) {
4478              // this is necessary for settings upgrade
4479              return null;
4480          }
4481          if (empty($result)) {
4482              $result = array('$@NONE@$');
4483          }
4484          return $result;
4485      }
4486  
4487      /**
4488       * Save the chosen setting provided as $data
4489       *
4490       * @param array $data
4491       * @return mixed string or array
4492       */
4493      public function write_setting($data) {
4494      // If all is selected, remove any explicit options.
4495          if (in_array('$@ALL@$', $data)) {
4496              $data = array('$@ALL@$');
4497          }
4498          // None never needs to be written to the DB.
4499          if (in_array('$@NONE@$', $data)) {
4500              unset($data[array_search('$@NONE@$', $data)]);
4501          }
4502          return parent::write_setting($data);
4503      }
4504  }
4505  
4506  
4507  /**
4508   * Special checkbox for calendar - resets SESSION vars.
4509   *
4510   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4511   */
4512  class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
4513      /**
4514       * Calls the parent::__construct with default values
4515       *
4516       * name =>  calendar_adminseesall
4517       * visiblename => get_string('adminseesall', 'admin')
4518       * description => get_string('helpadminseesall', 'admin')
4519       * defaultsetting => 0
4520       */
4521      public function __construct() {
4522          parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
4523              get_string('helpadminseesall', 'admin'), '0');
4524      }
4525  
4526      /**
4527       * Stores the setting passed in $data
4528       *
4529       * @param mixed gets converted to string for comparison
4530       * @return string empty string or error message
4531       */
4532      public function write_setting($data) {
4533          global $SESSION;
4534          return parent::write_setting($data);
4535      }
4536  }
4537  
4538  /**
4539   * Special select for settings that are altered in setup.php and can not be altered on the fly
4540   *
4541   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4542   */
4543  class admin_setting_special_selectsetup extends admin_setting_configselect {
4544      /**
4545       * Reads the setting directly from the database
4546       *
4547       * @return mixed
4548       */
4549      public function get_setting() {
4550      // read directly from db!
4551          return get_config(NULL, $this->name);
4552      }
4553  
4554      /**
4555       * Save the setting passed in $data
4556       *
4557       * @param string $data The setting to save
4558       * @return string empty or error message
4559       */
4560      public function write_setting($data) {
4561          global $CFG;
4562          // do not change active CFG setting!
4563          $current = $CFG->{$this->name};
4564          $result = parent::write_setting($data);
4565          $CFG->{$this->name} = $current;
4566          return $result;
4567      }
4568  }
4569  
4570  
4571  /**
4572   * Special select for frontpage - stores data in course table
4573   *
4574   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4575   */
4576  class admin_setting_sitesetselect extends admin_setting_configselect {
4577      /**
4578       * Returns the site name for the selected site
4579       *
4580       * @see get_site()
4581       * @return string The site name of the selected site
4582       */
4583      public function get_setting() {
4584          $site = course_get_format(get_site())->get_course();
4585          return $site->{$this->name};
4586      }
4587  
4588      /**
4589       * Updates the database and save the setting
4590       *
4591       * @param string data
4592       * @return string empty or error message
4593       */
4594      public function write_setting($data) {
4595          global $DB, $SITE, $COURSE;
4596          if (!in_array($data, array_keys($this->choices))) {
4597              return get_string('errorsetting', 'admin');
4598          }
4599          $record = new stdClass();
4600          $record->id           = SITEID;
4601          $temp                 = $this->name;
4602          $record->$temp        = $data;
4603          $record->timemodified = time();
4604  
4605          course_get_format($SITE)->update_course_format_options($record);
4606          $DB->update_record('course', $record);
4607  
4608          // Reset caches.
4609          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4610          if ($SITE->id == $COURSE->id) {
4611              $COURSE = $SITE;
4612          }
4613          core_courseformat\base::reset_course_cache($SITE->id);
4614  
4615          return '';
4616  
4617      }
4618  
4619      /**
4620       * admin_setting_sitesetselect is not meant to be overridden in config.php.
4621       *
4622       * @return bool
4623       */
4624      public function is_forceable(): bool {
4625          return false;
4626      }
4627  }
4628  
4629  
4630  /**
4631   * Select for blog's bloglevel setting: if set to 0, will set blog_menu
4632   * block to hidden.
4633   *
4634   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4635   */
4636  class admin_setting_bloglevel extends admin_setting_configselect {
4637      /**
4638       * Updates the database and save the setting
4639       *
4640       * @param string data
4641       * @return string empty or error message
4642       */
4643      public function write_setting($data) {
4644          global $DB, $CFG;
4645          if ($data == 0) {
4646              $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
4647              foreach ($blogblocks as $block) {
4648                  $DB->set_field('block', 'visible', 0, array('id' => $block->id));
4649              }
4650          } else {
4651              // reenable all blocks only when switching from disabled blogs
4652              if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
4653                  $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
4654                  foreach ($blogblocks as $block) {
4655                      $DB->set_field('block', 'visible', 1, array('id' => $block->id));
4656                  }
4657              }
4658          }
4659          return parent::write_setting($data);
4660      }
4661  }
4662  
4663  
4664  /**
4665   * Special select - lists on the frontpage - hacky
4666   *
4667   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4668   */
4669  class admin_setting_courselist_frontpage extends admin_setting {
4670  
4671      /** @var array Array of choices value=>label. */
4672      public $choices;
4673  
4674      /**
4675       * Construct override, requires one param
4676       *
4677       * @param bool $loggedin Is the user logged in
4678       */
4679      public function __construct($loggedin) {
4680          global $CFG;
4681          require_once($CFG->dirroot.'/course/lib.php');
4682          $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
4683          $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
4684          $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
4685          $defaults    = array(FRONTPAGEALLCOURSELIST);
4686          parent::__construct($name, $visiblename, $description, $defaults);
4687      }
4688  
4689      /**
4690       * Loads the choices available
4691       *
4692       * @return bool always returns true
4693       */
4694      public function load_choices() {
4695          if (is_array($this->choices)) {
4696              return true;
4697          }
4698          $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
4699              FRONTPAGEALLCOURSELIST => get_string('frontpagecourselist'),
4700              FRONTPAGEENROLLEDCOURSELIST => get_string('frontpageenrolledcourselist'),
4701              FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
4702              FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
4703              FRONTPAGECOURSESEARCH  => get_string('frontpagecoursesearch'),
4704              'none'                 => get_string('none'));
4705          if ($this->name === 'frontpage') {
4706              unset($this->choices[FRONTPAGEENROLLEDCOURSELIST]);
4707          }
4708          return true;
4709      }
4710  
4711      /**
4712       * Returns the selected settings
4713       *
4714       * @param mixed array or setting or null
4715       */
4716      public function get_setting() {
4717          $result = $this->config_read($this->name);
4718          if (is_null($result)) {
4719              return NULL;
4720          }
4721          if ($result === '') {
4722              return array();
4723          }
4724          return explode(',', $result);
4725      }
4726  
4727      /**
4728       * Save the selected options
4729       *
4730       * @param array $data
4731       * @return mixed empty string (data is not an array) or bool true=success false=failure
4732       */
4733      public function write_setting($data) {
4734          if (!is_array($data)) {
4735              return '';
4736          }
4737          $this->load_choices();
4738          $save = array();
4739          foreach($data as $datum) {
4740              if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
4741                  continue;
4742              }
4743              $save[$datum] = $datum; // no duplicates
4744          }
4745          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
4746      }
4747  
4748      /**
4749       * Return XHTML select field and wrapping div
4750       *
4751       * @todo Add vartype handling to make sure $data is an array
4752       * @param array $data Array of elements to select by default
4753       * @return string XHTML select field and wrapping div
4754       */
4755      public function output_html($data, $query='') {
4756          global $OUTPUT;
4757  
4758          $this->load_choices();
4759          $currentsetting = array();
4760          foreach ($data as $key) {
4761              if ($key != 'none' and array_key_exists($key, $this->choices)) {
4762                  $currentsetting[] = $key; // already selected first
4763              }
4764          }
4765  
4766          $context = (object) [
4767              'id' => $this->get_id(),
4768              'name' => $this->get_full_name(),
4769          ];
4770  
4771          $options = $this->choices;
4772          $selects = [];
4773          for ($i = 0; $i < count($this->choices) - 1; $i++) {
4774              if (!array_key_exists($i, $currentsetting)) {
4775                  $currentsetting[$i] = 'none';
4776              }
4777              $selects[] = [
4778                  'key' => $i,
4779                  'options' => array_map(function($option) use ($options, $currentsetting, $i) {
4780                      return [
4781                          'name' => $options[$option],
4782                          'value' => $option,
4783                          'selected' => $currentsetting[$i] == $option
4784                      ];
4785                  }, array_keys($options))
4786              ];
4787          }
4788          $context->selects = $selects;
4789  
4790          $element = $OUTPUT->render_from_template('core_admin/setting_courselist_frontpage', $context);
4791  
4792          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
4793      }
4794  }
4795  
4796  
4797  /**
4798   * Special checkbox for frontpage - stores data in course table
4799   *
4800   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4801   */
4802  class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
4803      /**
4804       * Returns the current sites name
4805       *
4806       * @return string
4807       */
4808      public function get_setting() {
4809          $site = course_get_format(get_site())->get_course();
4810          return $site->{$this->name};
4811      }
4812  
4813      /**
4814       * Save the selected setting
4815       *
4816       * @param string $data The selected site
4817       * @return string empty string or error message
4818       */
4819      public function write_setting($data) {
4820          global $DB, $SITE, $COURSE;
4821          $record = new stdClass();
4822          $record->id            = $SITE->id;
4823          $record->{$this->name} = ($data == '1' ? 1 : 0);
4824          $record->timemodified  = time();
4825  
4826          course_get_format($SITE)->update_course_format_options($record);
4827          $DB->update_record('course', $record);
4828  
4829          // Reset caches.
4830          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4831          if ($SITE->id == $COURSE->id) {
4832              $COURSE = $SITE;
4833          }
4834          core_courseformat\base::reset_course_cache($SITE->id);
4835  
4836          return '';
4837      }
4838  
4839      /**
4840       * admin_setting_sitesetcheckbox is not meant to be overridden in config.php.
4841       *
4842       * @return bool
4843       */
4844      public function is_forceable(): bool {
4845          return false;
4846      }
4847  }
4848  
4849  /**
4850   * Special text for frontpage - stores data in course table.
4851   * Empty string means not set here. Manual setting is required.
4852   *
4853   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4854   */
4855  class admin_setting_sitesettext extends admin_setting_configtext {
4856  
4857      /**
4858       * Constructor.
4859       */
4860      public function __construct() {
4861          call_user_func_array([parent::class, '__construct'], func_get_args());
4862          $this->set_force_ltr(false);
4863      }
4864  
4865      /**
4866       * Return the current setting
4867       *
4868       * @return mixed string or null
4869       */
4870      public function get_setting() {
4871          $site = course_get_format(get_site())->get_course();
4872          return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
4873      }
4874  
4875      /**
4876       * Validate the selected data
4877       *
4878       * @param string $data The selected value to validate
4879       * @return mixed true or message string
4880       */
4881      public function validate($data) {
4882          global $DB, $SITE;
4883          $cleaned = clean_param($data, PARAM_TEXT);
4884          if ($cleaned === '') {
4885              return get_string('required');
4886          }
4887          if ($this->name ==='shortname' &&
4888                  $DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data, $SITE->id))) {
4889              return get_string('shortnametaken', 'error', $data);
4890          }
4891          if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
4892              return true;
4893          } else {
4894              return get_string('validateerror', 'admin');
4895          }
4896      }
4897  
4898      /**
4899       * Save the selected setting
4900       *
4901       * @param string $data The selected value
4902       * @return string empty or error message
4903       */
4904      public function write_setting($data) {
4905          global $DB, $SITE, $COURSE;
4906          $data = trim($data);
4907          $validated = $this->validate($data);
4908          if ($validated !== true) {
4909              return $validated;
4910          }
4911  
4912          $record = new stdClass();
4913          $record->id            = $SITE->id;
4914          $record->{$this->name} = $data;
4915          $record->timemodified  = time();
4916  
4917          course_get_format($SITE)->update_course_format_options($record);
4918          $DB->update_record('course', $record);
4919  
4920          // Reset caches.
4921          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4922          if ($SITE->id == $COURSE->id) {
4923              $COURSE = $SITE;
4924          }
4925          core_courseformat\base::reset_course_cache($SITE->id);
4926  
4927          return '';
4928      }
4929  
4930      /**
4931       * admin_setting_sitesettext is not meant to be overridden in config.php.
4932       *
4933       * @return bool
4934       */
4935      public function is_forceable(): bool {
4936          return false;
4937      }
4938  }
4939  
4940  
4941  /**
4942   * This type of field should be used for mandatory config settings.
4943   *
4944   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4945   */
4946  class admin_setting_requiredtext extends admin_setting_configtext {
4947  
4948      /**
4949       * Validate data before storage.
4950       *
4951       * @param string $data The string to be validated.
4952       * @return bool|string true for success or error string if invalid.
4953       */
4954      public function validate($data) {
4955          $cleaned = clean_param($data, PARAM_TEXT);
4956          if ($cleaned === '') {
4957              return get_string('required');
4958          }
4959  
4960          return parent::validate($data);
4961      }
4962  }
4963  
4964  /**
4965   * This type of field should be used for mandatory config settings where setting password is required.
4966   *
4967   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4968   */
4969  class admin_setting_requiredpasswordunmask extends admin_setting_configpasswordunmask {
4970  
4971      /**
4972       * Validate data before storage.
4973       *
4974       * @param string $data The string to be validated.
4975       * @return bool|string true for success or error string if invalid.
4976       */
4977      public function validate($data) {
4978          $cleaned = clean_param($data, PARAM_TEXT);
4979          if ($cleaned === '') {
4980              return get_string('required');
4981          }
4982  
4983          return parent::validate($data);
4984      }
4985  }
4986  
4987  /**
4988   * Special text editor for site description.
4989   *
4990   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4991   */
4992  class admin_setting_special_frontpagedesc extends admin_setting_confightmleditor {
4993  
4994      /**
4995       * Calls parent::__construct with specific arguments
4996       */
4997      public function __construct() {
4998          parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), null,
4999              PARAM_RAW, 60, 15);
5000      }
5001  
5002      /**
5003       * Return the current setting
5004       * @return string The current setting
5005       */
5006      public function get_setting() {
5007          $site = course_get_format(get_site())->get_course();
5008          return $site->{$this->name};
5009      }
5010  
5011      /**
5012       * Save the new setting
5013       *
5014       * @param string $data The new value to save
5015       * @return string empty or error message
5016       */
5017      public function write_setting($data) {
5018          global $DB, $SITE, $COURSE;
5019          $record = new stdClass();
5020          $record->id            = $SITE->id;
5021          $record->{$this->name} = $data;
5022          $record->timemodified  = time();
5023  
5024          course_get_format($SITE)->update_course_format_options($record);
5025          $DB->update_record('course', $record);
5026  
5027          // Reset caches.
5028          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
5029          if ($SITE->id == $COURSE->id) {
5030              $COURSE = $SITE;
5031          }
5032          core_courseformat\base::reset_course_cache($SITE->id);
5033  
5034          return '';
5035      }
5036  
5037      /**
5038       * admin_setting_special_frontpagedesc is not meant to be overridden in config.php.
5039       *
5040       * @return bool
5041       */
5042      public function is_forceable(): bool {
5043          return false;
5044      }
5045  }
5046  
5047  
5048  /**
5049   * Administration interface for emoticon_manager settings.
5050   *
5051   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5052   */
5053  class admin_setting_emoticons extends admin_setting {
5054  
5055      /**
5056       * Calls parent::__construct with specific args
5057       */
5058      public function __construct() {
5059          global $CFG;
5060  
5061          $manager = get_emoticon_manager();
5062          $defaults = $this->prepare_form_data($manager->default_emoticons());
5063          parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
5064      }
5065  
5066      /**
5067       * Return the current setting(s)
5068       *
5069       * @return array Current settings array
5070       */
5071      public function get_setting() {
5072          global $CFG;
5073  
5074          $manager = get_emoticon_manager();
5075  
5076          $config = $this->config_read($this->name);
5077          if (is_null($config)) {
5078              return null;
5079          }
5080  
5081          $config = $manager->decode_stored_config($config);
5082          if (is_null($config)) {
5083              return null;
5084          }
5085  
5086          return $this->prepare_form_data($config);
5087      }
5088  
5089      /**
5090       * Save selected settings
5091       *
5092       * @param array $data Array of settings to save
5093       * @return bool
5094       */
5095      public function write_setting($data) {
5096  
5097          $manager = get_emoticon_manager();
5098          $emoticons = $this->process_form_data($data);
5099  
5100          if ($emoticons === false) {
5101              return false;
5102          }
5103  
5104          if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
5105              return ''; // success
5106          } else {
5107              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
5108          }
5109      }
5110  
5111      /**
5112       * Return XHTML field(s) for options
5113       *
5114       * @param array $data Array of options to set in HTML
5115       * @return string XHTML string for the fields and wrapping div(s)
5116       */
5117      public function output_html($data, $query='') {
5118          global $OUTPUT;
5119  
5120          $context = (object) [
5121              'name' => $this->get_full_name(),
5122              'emoticons' => [],
5123              'forceltr' => true,
5124          ];
5125  
5126          $i = 0;
5127          foreach ($data as $field => $value) {
5128  
5129              // When $i == 0: text.
5130              // When $i == 1: imagename.
5131              // When $i == 2: imagecomponent.
5132              // When $i == 3: altidentifier.
5133              // When $i == 4: altcomponent.
5134              $fields[$i] = (object) [
5135                  'field' => $field,
5136                  'value' => $value,
5137                  'index' => $i
5138              ];
5139              $i++;
5140  
5141              if ($i > 4) {
5142                  $icon = null;
5143                  if (!empty($fields[1]->value)) {
5144                      if (get_string_manager()->string_exists($fields[3]->value, $fields[4]->value)) {
5145                          $alt = get_string($fields[3]->value, $fields[4]->value);
5146                      } else {
5147                          $alt = $fields[0]->value;
5148                      }
5149                      $icon = new pix_emoticon($fields[1]->value, $alt, $fields[2]->value);
5150                  }
5151                  $context->emoticons[] = [
5152                      'fields' => $fields,
5153                      'icon' => $icon ? $icon->export_for_template($OUTPUT) : null
5154                  ];
5155                  $fields = [];
5156                  $i = 0;
5157              }
5158          }
5159  
5160          $context->reseturl = new moodle_url('/admin/resetemoticons.php');
5161          $element = $OUTPUT->render_from_template('core_admin/setting_emoticons', $context);
5162          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
5163      }
5164  
5165      /**
5166       * Converts the array of emoticon objects provided by {@see emoticon_manager} into admin settings form data
5167       *
5168       * @see self::process_form_data()
5169       * @param array $emoticons array of emoticon objects as returned by {@see emoticon_manager}
5170       * @return array of form fields and their values
5171       */
5172      protected function prepare_form_data(array $emoticons) {
5173  
5174          $form = array();
5175          $i = 0;
5176          foreach ($emoticons as $emoticon) {
5177              $form['text'.$i]            = $emoticon->text;
5178              $form['imagename'.$i]       = $emoticon->imagename;
5179              $form['imagecomponent'.$i]  = $emoticon->imagecomponent;
5180              $form['altidentifier'.$i]   = $emoticon->altidentifier;
5181              $form['altcomponent'.$i]    = $emoticon->altcomponent;
5182              $i++;
5183          }
5184          // add one more blank field set for new object
5185          $form['text'.$i]            = '';
5186          $form['imagename'.$i]       = '';
5187          $form['imagecomponent'.$i]  = '';
5188          $form['altidentifier'.$i]   = '';
5189          $form['altcomponent'.$i]    = '';
5190  
5191          return $form;
5192      }
5193  
5194      /**
5195       * Converts the data from admin settings form into an array of emoticon objects
5196       *
5197       * @see self::prepare_form_data()
5198       * @param array $data array of admin form fields and values
5199       * @return false|array of emoticon objects
5200       */
5201      protected function process_form_data(array $form) {
5202  
5203          $count = count($form); // number of form field values
5204  
5205          if ($count % 5) {
5206              // we must get five fields per emoticon object
5207              return false;
5208          }
5209  
5210          $emoticons = array();
5211          for ($i = 0; $i < $count / 5; $i++) {
5212              $emoticon                   = new stdClass();
5213              $emoticon->text             = clean_param(trim($form['text'.$i]), PARAM_NOTAGS);
5214              $emoticon->imagename        = clean_param(trim($form['imagename'.$i]), PARAM_PATH);
5215              $emoticon->imagecomponent   = clean_param(trim($form['imagecomponent'.$i]), PARAM_COMPONENT);
5216              $emoticon->altidentifier    = clean_param(trim($form['altidentifier'.$i]), PARAM_STRINGID);
5217              $emoticon->altcomponent     = clean_param(trim($form['altcomponent'.$i]), PARAM_COMPONENT);
5218  
5219              if (strpos($emoticon->text, ':/') !== false or strpos($emoticon->text, '//') !== false) {
5220                  // prevent from breaking http://url.addresses by accident
5221                  $emoticon->text = '';
5222              }
5223  
5224              if (strlen($emoticon->text) < 2) {
5225                  // do not allow single character emoticons
5226                  $emoticon->text = '';
5227              }
5228  
5229              if (preg_match('/^[a-zA-Z]+[a-zA-Z0-9]*$/', $emoticon->text)) {
5230                  // emoticon text must contain some non-alphanumeric character to prevent
5231                  // breaking HTML tags
5232                  $emoticon->text = '';
5233              }
5234  
5235              if ($emoticon->text !== '' and $emoticon->imagename !== '' and $emoticon->imagecomponent !== '') {
5236                  $emoticons[] = $emoticon;
5237              }
5238          }
5239          return $emoticons;
5240      }
5241  
5242  }
5243  
5244  
5245  /**
5246   * Special setting for limiting of the list of available languages.
5247   *
5248   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5249   */
5250  class admin_setting_langlist extends admin_setting_configtext {
5251      /**
5252       * Calls parent::__construct with specific arguments
5253       */
5254      public function __construct() {
5255          parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
5256      }
5257  
5258      /**
5259       * Validate that each language identifier exists on the site
5260       *
5261       * @param string $data
5262       * @return bool|string True if validation successful, otherwise error string
5263       */
5264      public function validate($data) {
5265          $parentcheck = parent::validate($data);
5266          if ($parentcheck !== true) {
5267              return $parentcheck;
5268          }
5269  
5270          if ($data === '') {
5271              return true;
5272          }
5273  
5274          // Normalize language identifiers.
5275          $langcodes = array_map('trim', explode(',', $data));
5276          foreach ($langcodes as $langcode) {
5277              // If the langcode contains optional alias, split it out.
5278              [$langcode, ] = preg_split('/\s*\|\s*/', $langcode, 2);
5279  
5280              if (!get_string_manager()->translation_exists($langcode)) {
5281                  return get_string('invalidlanguagecode', 'error', $langcode);
5282              }
5283          }
5284  
5285          return true;
5286      }
5287  
5288      /**
5289       * Save the new setting
5290       *
5291       * @param string $data The new setting
5292       * @return bool
5293       */
5294      public function write_setting($data) {
5295          $return = parent::write_setting($data);
5296          get_string_manager()->reset_caches();
5297          return $return;
5298      }
5299  }
5300  
5301  
5302  /**
5303   * Allows to specify comma separated list of known country codes.
5304   *
5305   * This is a simple subclass of the plain input text field with added validation so that all the codes are actually
5306   * known codes.
5307   *
5308   * @package     core
5309   * @category    admin
5310   * @copyright   2020 David Mudrák <david@moodle.com>
5311   * @license     https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5312   */
5313  class admin_setting_countrycodes extends admin_setting_configtext {
5314  
5315      /**
5316       * Construct the instance of the setting.
5317       *
5318       * @param string $name Name of the admin setting such as 'allcountrycodes' or 'myplugin/countries'.
5319       * @param lang_string|string $visiblename Language string with the field label text.
5320       * @param lang_string|string $description Language string with the field description text.
5321       * @param string $defaultsetting Default value of the setting.
5322       * @param int $size Input text field size.
5323       */
5324      public function __construct($name, $visiblename, $description, $defaultsetting = '', $size = null) {
5325          parent::__construct($name, $visiblename, $description, $defaultsetting, '/^(?:\w+(?:,\w+)*)?$/', $size);
5326      }
5327  
5328      /**
5329       * Validate the setting value before storing it.
5330       *
5331       * The value is first validated through custom regex so that it is a word consisting of letters, numbers or underscore; or
5332       * a comma separated list of such words.
5333       *
5334       * @param string $data Value inserted into the setting field.
5335       * @return bool|string True if the value is OK, error string otherwise.
5336       */
5337      public function validate($data) {
5338  
5339          $parentcheck = parent::validate($data);
5340  
5341          if ($parentcheck !== true) {
5342              return $parentcheck;
5343          }
5344  
5345          if ($data === '') {
5346              return true;
5347          }
5348  
5349          $allcountries = get_string_manager()->get_list_of_countries(true);
5350  
5351          foreach (explode(',', $data) as $code) {
5352              if (!isset($allcountries[$code])) {
5353                  return get_string('invalidcountrycode', 'core_error', $code);
5354              }
5355          }
5356  
5357          return true;
5358      }
5359  }
5360  
5361  
5362  /**
5363   * Selection of one of the recognised countries using the list
5364   * returned by {@link get_list_of_countries()}.
5365   *
5366   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5367   */
5368  class admin_settings_country_select extends admin_setting_configselect {
5369      protected $includeall;
5370      public function __construct($name, $visiblename, $description, $defaultsetting, $includeall=false) {
5371          $this->includeall = $includeall;
5372          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
5373      }
5374  
5375      /**
5376       * Lazy-load the available choices for the select box
5377       */
5378      public function load_choices() {
5379          global $CFG;
5380          if (is_array($this->choices)) {
5381              return true;
5382          }
5383          $this->choices = array_merge(
5384                  array('0' => get_string('choosedots')),
5385                  get_string_manager()->get_list_of_countries($this->includeall));
5386          return true;
5387      }
5388  }
5389  
5390  
5391  /**
5392   * admin_setting_configselect for the default number of sections in a course,
5393   * simply so we can lazy-load the choices.
5394   *
5395   * @copyright 2011 The Open University
5396   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5397   */
5398  class admin_settings_num_course_sections extends admin_setting_configselect {
5399      public function __construct($name, $visiblename, $description, $defaultsetting) {
5400          parent::__construct($name, $visiblename, $description, $defaultsetting, array());
5401      }
5402  
5403      /** Lazy-load the available choices for the select box */
5404      public function load_choices() {
5405          $max = get_config('moodlecourse', 'maxsections');
5406          if (!isset($max) || !is_numeric($max)) {
5407              $max = 52;
5408          }
5409          for ($i = 0; $i <= $max; $i++) {
5410              $this->choices[$i] = "$i";
5411          }
5412          return true;
5413      }
5414  }
5415  
5416  
5417  /**
5418   * Course category selection
5419   *
5420   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5421   */
5422  class admin_settings_coursecat_select extends admin_setting_configselect_autocomplete {
5423      /**
5424       * Calls parent::__construct with specific arguments
5425       */
5426      public function __construct($name, $visiblename, $description, $defaultsetting = 1) {
5427          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices = null);
5428      }
5429  
5430      /**
5431       * Load the available choices for the select box
5432       *
5433       * @return bool
5434       */
5435      public function load_choices() {
5436          if (is_array($this->choices)) {
5437              return true;
5438          }
5439          $this->choices = core_course_category::make_categories_list('', 0, ' / ');
5440          return true;
5441      }
5442  }
5443  
5444  
5445  /**
5446   * Special control for selecting days to backup
5447   *
5448   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5449   */
5450  class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
5451      /**
5452       * Calls parent::__construct with specific arguments
5453       */
5454      public function __construct() {
5455          parent::__construct('backup_auto_weekdays', get_string('automatedbackupschedule','backup'), get_string('automatedbackupschedulehelp','backup'), array(), NULL);
5456          $this->plugin = 'backup';
5457      }
5458  
5459      /**
5460       * Load the available choices for the select box
5461       *
5462       * @return bool Always returns true
5463       */
5464      public function load_choices() {
5465          if (is_array($this->choices)) {
5466              return true;
5467          }
5468          $this->choices = array();
5469          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5470          foreach ($days as $day) {
5471              $this->choices[$day] = get_string($day, 'calendar');
5472          }
5473          return true;
5474      }
5475  }
5476  
5477  /**
5478   * Special setting for backup auto destination.
5479   *
5480   * @package    core
5481   * @subpackage admin
5482   * @copyright  2014 Frédéric Massart - FMCorz.net
5483   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5484   */
5485  class admin_setting_special_backup_auto_destination extends admin_setting_configdirectory {
5486  
5487      /**
5488       * Calls parent::__construct with specific arguments.
5489       */
5490      public function __construct() {
5491          parent::__construct('backup/backup_auto_destination', new lang_string('saveto'), new lang_string('backupsavetohelp'), '');
5492      }
5493  
5494      /**
5495       * Check if the directory must be set, depending on backup/backup_auto_storage.
5496       *
5497       * Note: backup/backup_auto_storage must be specified BEFORE this setting otherwise
5498       * there will be conflicts if this validation happens before the other one.
5499       *
5500       * @param string $data Form data.
5501       * @return string Empty when no errors.
5502       */
5503      public function write_setting($data) {
5504          $storage = (int) get_config('backup', 'backup_auto_storage');
5505          if ($storage !== 0) {
5506              if (empty($data) || !file_exists($data) || !is_dir($data) || !is_writable($data) ) {
5507                  // The directory must exist and be writable.
5508                  return get_string('backuperrorinvaliddestination');
5509              }
5510          }
5511          return parent::write_setting($data);
5512      }
5513  }
5514  
5515  
5516  /**
5517   * Special debug setting
5518   *
5519   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5520   */
5521  class admin_setting_special_debug extends admin_setting_configselect {
5522      /**
5523       * Calls parent::__construct with specific arguments
5524       */
5525      public function __construct() {
5526          parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
5527      }
5528  
5529      /**
5530       * Load the available choices for the select box
5531       *
5532       * @return bool
5533       */
5534      public function load_choices() {
5535          if (is_array($this->choices)) {
5536              return true;
5537          }
5538          $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
5539              DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
5540              DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
5541              DEBUG_ALL       => get_string('debugall', 'admin'),
5542              DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
5543          return true;
5544      }
5545  }
5546  
5547  
5548  /**
5549   * Special admin control
5550   *
5551   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5552   */
5553  class admin_setting_special_calendar_weekend extends admin_setting {
5554      /**
5555       * Calls parent::__construct with specific arguments
5556       */
5557      public function __construct() {
5558          $name = 'calendar_weekend';
5559          $visiblename = get_string('calendar_weekend', 'admin');
5560          $description = get_string('helpweekenddays', 'admin');
5561          $default = array ('0', '6'); // Saturdays and Sundays
5562          parent::__construct($name, $visiblename, $description, $default);
5563      }
5564  
5565      /**
5566       * Gets the current settings as an array
5567       *
5568       * @return mixed Null if none, else array of settings
5569       */
5570      public function get_setting() {
5571          $result = $this->config_read($this->name);
5572          if (is_null($result)) {
5573              return NULL;
5574          }
5575          if ($result === '') {
5576              return array();
5577          }
5578          $settings = array();
5579          for ($i=0; $i<7; $i++) {
5580              if ($result & (1 << $i)) {
5581                  $settings[] = $i;
5582              }
5583          }
5584          return $settings;
5585      }
5586  
5587      /**
5588       * Save the new settings
5589       *
5590       * @param array $data Array of new settings
5591       * @return bool
5592       */
5593      public function write_setting($data) {
5594          if (!is_array($data)) {
5595              return '';
5596          }
5597          unset($data['xxxxx']);
5598          $result = 0;
5599          foreach($data as $index) {
5600              $result |= 1 << $index;
5601          }
5602          return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
5603      }
5604  
5605      /**
5606       * Return XHTML to display the control
5607       *
5608       * @param array $data array of selected days
5609       * @param string $query
5610       * @return string XHTML for display (field + wrapping div(s)
5611       */
5612      public function output_html($data, $query='') {
5613          global $OUTPUT;
5614  
5615          // The order matters very much because of the implied numeric keys.
5616          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5617          $context = (object) [
5618              'name' => $this->get_full_name(),
5619              'id' => $this->get_id(),
5620              'days' => array_map(function($index) use ($days, $data) {
5621                  return [
5622                      'index' => $index,
5623                      'label' => get_string($days[$index], 'calendar'),
5624                      'checked' => in_array($index, $data)
5625                  ];
5626              }, array_keys($days))
5627          ];
5628  
5629          $element = $OUTPUT->render_from_template('core_admin/setting_special_calendar_weekend', $context);
5630  
5631          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
5632  
5633      }
5634  }
5635  
5636  
5637  /**
5638   * Admin setting that allows a user to pick a behaviour.
5639   *
5640   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5641   */
5642  class admin_setting_question_behaviour extends admin_setting_configselect {
5643      /**
5644       * @param string $name name of config variable
5645       * @param string $visiblename display name
5646       * @param string $description description
5647       * @param string $default default.
5648       */
5649      public function __construct($name, $visiblename, $description, $default) {
5650          parent::__construct($name, $visiblename, $description, $default, null);
5651      }
5652  
5653      /**
5654       * Load list of behaviours as choices
5655       * @return bool true => success, false => error.
5656       */
5657      public function load_choices() {
5658          global $CFG;
5659          require_once($CFG->dirroot . '/question/engine/lib.php');
5660          $this->choices = question_engine::get_behaviour_options('');
5661          return true;
5662      }
5663  }
5664  
5665  
5666  /**
5667   * Admin setting that allows a user to pick appropriate roles for something.
5668   *
5669   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5670   */
5671  class admin_setting_pickroles extends admin_setting_configmulticheckbox {
5672      /** @var array Array of capabilities which identify roles */
5673      private $types;
5674  
5675      /**
5676       * @param string $name Name of config variable
5677       * @param string $visiblename Display name
5678       * @param string $description Description
5679       * @param array $types Array of archetypes which identify
5680       *              roles that will be enabled by default.
5681       */
5682      public function __construct($name, $visiblename, $description, $types) {
5683          parent::__construct($name, $visiblename, $description, NULL, NULL);
5684          $this->types = $types;
5685      }
5686  
5687      /**
5688       * Load roles as choices
5689       *
5690       * @return bool true=>success, false=>error
5691       */
5692      public function load_choices() {
5693          global $CFG, $DB;
5694          if (during_initial_install()) {
5695              return false;
5696          }
5697          if (is_array($this->choices)) {
5698              return true;
5699          }
5700          if ($roles = get_all_roles()) {
5701              $this->choices = role_fix_names($roles, null, ROLENAME_ORIGINAL, true);
5702              return true;
5703          } else {
5704              return false;
5705          }
5706      }
5707  
5708      /**
5709       * Return the default setting for this control
5710       *
5711       * @return array Array of default settings
5712       */
5713      public function get_defaultsetting() {
5714          global $CFG;
5715  
5716          if (during_initial_install()) {
5717              return null;
5718          }
5719          $result = array();
5720          foreach($this->types as $archetype) {
5721              if ($caproles = get_archetype_roles($archetype)) {
5722                  foreach ($caproles as $caprole) {
5723                      $result[$caprole->id] = 1;
5724                  }
5725              }
5726          }
5727          return $result;
5728      }
5729  }
5730  
5731  
5732  /**
5733   * Admin setting that is a list of installed filter plugins.
5734   *
5735   * @copyright 2015 The Open University
5736   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5737   */
5738  class admin_setting_pickfilters extends admin_setting_configmulticheckbox {
5739  
5740      /**
5741       * Constructor
5742       *
5743       * @param string $name unique ascii name, either 'mysetting' for settings
5744       *      that in config, or 'myplugin/mysetting' for ones in config_plugins.
5745       * @param string $visiblename localised name
5746       * @param string $description localised long description
5747       * @param array $default the default. E.g. array('urltolink' => 1, 'emoticons' => 1)
5748       */
5749      public function __construct($name, $visiblename, $description, $default) {
5750          if (empty($default)) {
5751              $default = array();
5752          }
5753          $this->load_choices();
5754          foreach ($default as $plugin) {
5755              if (!isset($this->choices[$plugin])) {
5756                  unset($default[$plugin]);
5757              }
5758          }
5759          parent::__construct($name, $visiblename, $description, $default, null);
5760      }
5761  
5762      public function load_choices() {
5763          if (is_array($this->choices)) {
5764              return true;
5765          }
5766          $this->choices = array();
5767  
5768          foreach (core_component::get_plugin_list('filter') as $plugin => $unused) {
5769              $this->choices[$plugin] = filter_get_name($plugin);
5770          }
5771          return true;
5772      }
5773  }
5774  
5775  
5776  /**
5777   * Text field with an advanced checkbox, that controls a additional $name.'_adv' setting.
5778   *
5779   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5780   */
5781  class admin_setting_configtext_with_advanced extends admin_setting_configtext {
5782      /**
5783       * Constructor
5784       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5785       * @param string $visiblename localised
5786       * @param string $description long localised info
5787       * @param array $defaultsetting ('value'=>string, '__construct'=>bool)
5788       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
5789       * @param int $size default field size
5790       */
5791      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
5792          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $paramtype, $size);
5793          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5794      }
5795  }
5796  
5797  
5798  /**
5799   * Checkbox with an advanced checkbox that controls an additional $name.'_adv' config setting.
5800   *
5801   * @copyright 2009 Petr Skoda (http://skodak.org)
5802   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5803   */
5804  class admin_setting_configcheckbox_with_advanced extends admin_setting_configcheckbox {
5805  
5806      /**
5807       * Constructor
5808       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5809       * @param string $visiblename localised
5810       * @param string $description long localised info
5811       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
5812       * @param string $yes value used when checked
5813       * @param string $no value used when not checked
5814       */
5815      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5816          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5817          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5818      }
5819  
5820  }
5821  
5822  
5823  /**
5824   * Checkbox with an advanced checkbox that controls an additional $name.'_locked' config setting.
5825   *
5826   * This is nearly a copy/paste of admin_setting_configcheckbox_with_adv
5827   *
5828   * @copyright 2010 Sam Hemelryk
5829   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5830   */
5831  class admin_setting_configcheckbox_with_lock extends admin_setting_configcheckbox {
5832      /**
5833       * Constructor
5834       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5835       * @param string $visiblename localised
5836       * @param string $description long localised info
5837       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5838       * @param string $yes value used when checked
5839       * @param string $no value used when not checked
5840       */
5841      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5842          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5843          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5844      }
5845  
5846  }
5847  
5848  /**
5849   * Autocomplete as you type form element.
5850   *
5851   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5852   */
5853  class admin_setting_configselect_autocomplete extends admin_setting_configselect {
5854      /** @var boolean $tags Should we allow typing new entries to the field? */
5855      protected $tags = false;
5856      /** @var string $ajax Name of an AMD module to send/process ajax requests. */
5857      protected $ajax = '';
5858      /** @var string $placeholder Placeholder text for an empty list. */
5859      protected $placeholder = '';
5860      /** @var bool $casesensitive Whether the search has to be case-sensitive. */
5861      protected $casesensitive = false;
5862      /** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
5863      protected $showsuggestions = true;
5864      /** @var string $noselectionstring String that is shown when there are no selections. */
5865      protected $noselectionstring = '';
5866  
5867      /**
5868       * Returns XHTML select field and wrapping div(s)
5869       *
5870       * @see output_select_html()
5871       *
5872       * @param string $data the option to show as selected
5873       * @param string $query
5874       * @return string XHTML field and wrapping div
5875       */
5876      public function output_html($data, $query='') {
5877          global $PAGE;
5878  
5879          $html = parent::output_html($data, $query);
5880  
5881          if ($html === '') {
5882              return $html;
5883          }
5884  
5885          $this->placeholder = get_string('search');
5886  
5887          $params = array('#' . $this->get_id(), $this->tags, $this->ajax,
5888              $this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring);
5889  
5890          // Load autocomplete wrapper for select2 library.
5891          $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params);
5892  
5893          return $html;
5894      }
5895  }
5896  
5897  /**
5898   * Dropdown menu with an advanced checkbox, that controls a additional $name.'_adv' setting.
5899   *
5900   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5901   */
5902  class admin_setting_configselect_with_advanced extends admin_setting_configselect {
5903      /**
5904       * Calls parent::__construct with specific arguments
5905       */
5906      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5907          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5908          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5909      }
5910  
5911  }
5912  
5913  /**
5914   * Select with an advanced checkbox that controls an additional $name.'_locked' config setting.
5915   *
5916   * @copyright 2017 Marina Glancy
5917   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5918   */
5919  class admin_setting_configselect_with_lock extends admin_setting_configselect {
5920      /**
5921       * Constructor
5922       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
5923       *     or 'myplugin/mysetting' for ones in config_plugins.
5924       * @param string $visiblename localised
5925       * @param string $description long localised info
5926       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5927       * @param array $choices array of $value=>$label for each selection
5928       */
5929      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5930          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5931          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5932      }
5933  }
5934  
5935  
5936  /**
5937   * Graded roles in gradebook
5938   *
5939   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5940   */
5941  class admin_setting_special_gradebookroles extends admin_setting_pickroles {
5942      /**
5943       * Calls parent::__construct with specific arguments
5944       */
5945      public function __construct() {
5946          parent::__construct('gradebookroles', get_string('gradebookroles', 'admin'),
5947              get_string('configgradebookroles', 'admin'),
5948              array('student'));
5949      }
5950  }
5951  
5952  
5953  /**
5954   *
5955   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5956   */
5957  class admin_setting_regradingcheckbox extends admin_setting_configcheckbox {
5958      /**
5959       * Saves the new settings passed in $data
5960       *
5961       * @param string $data
5962       * @return mixed string or Array
5963       */
5964      public function write_setting($data) {
5965          global $CFG, $DB;
5966  
5967          $oldvalue  = $this->config_read($this->name);
5968          $return    = parent::write_setting($data);
5969          $newvalue  = $this->config_read($this->name);
5970  
5971          if ($oldvalue !== $newvalue) {
5972          // force full regrading
5973              $DB->set_field('grade_items', 'needsupdate', 1, array('needsupdate'=>0));
5974          }
5975  
5976          return $return;
5977      }
5978  }
5979  
5980  
5981  /**
5982   * Which roles to show on course description page
5983   *
5984   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5985   */
5986  class admin_setting_special_coursecontact extends admin_setting_pickroles {
5987      /**
5988       * Calls parent::__construct with specific arguments
5989       */
5990      public function __construct() {
5991          parent::__construct('coursecontact', get_string('coursecontact', 'admin'),
5992              get_string('coursecontact_desc', 'admin'),
5993              array('editingteacher'));
5994          $this->set_updatedcallback(function (){
5995              cache::make('core', 'coursecontacts')->purge();
5996          });
5997      }
5998  }
5999  
6000  
6001  /**
6002   *
6003   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6004   */
6005  class admin_setting_special_gradelimiting extends admin_setting_configcheckbox {
6006      /**
6007       * Calls parent::__construct with specific arguments
6008       */
6009      public function __construct() {
6010          parent::__construct('unlimitedgrades', get_string('unlimitedgrades', 'grades'),
6011              get_string('unlimitedgrades_help', 'grades'), '0', '1', '0');
6012      }
6013  
6014      /**
6015       * Old syntax of class constructor. Deprecated in PHP7.
6016       *
6017       * @deprecated since Moodle 3.1
6018       */
6019      public function admin_setting_special_gradelimiting() {
6020          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
6021          self::__construct();
6022      }
6023  
6024      /**
6025       * Force site regrading
6026       */
6027      function regrade_all() {
6028          global $CFG;
6029          require_once("$CFG->libdir/gradelib.php");
6030          grade_force_site_regrading();
6031      }
6032  
6033      /**
6034       * Saves the new settings
6035       *
6036       * @param mixed $data
6037       * @return string empty string or error message
6038       */
6039      function write_setting($data) {
6040          $previous = $this->get_setting();
6041  
6042          if ($previous === null) {
6043              if ($data) {
6044                  $this->regrade_all();
6045              }
6046          } else {
6047              if ($data != $previous) {
6048                  $this->regrade_all();
6049              }
6050          }
6051          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6052      }
6053  
6054  }
6055  
6056  /**
6057   * Special setting for $CFG->grade_minmaxtouse.
6058   *
6059   * @package    core
6060   * @copyright  2015 Frédéric Massart - FMCorz.net
6061   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6062   */
6063  class admin_setting_special_grademinmaxtouse extends admin_setting_configselect {
6064  
6065      /**
6066       * Constructor.
6067       */
6068      public function __construct() {
6069          parent::__construct('grade_minmaxtouse', new lang_string('minmaxtouse', 'grades'),
6070              new lang_string('minmaxtouse_desc', 'grades'), GRADE_MIN_MAX_FROM_GRADE_ITEM,
6071              array(
6072                  GRADE_MIN_MAX_FROM_GRADE_ITEM => get_string('gradeitemminmax', 'grades'),
6073                  GRADE_MIN_MAX_FROM_GRADE_GRADE => get_string('gradegrademinmax', 'grades')
6074              )
6075          );
6076      }
6077  
6078      /**
6079       * Saves the new setting.
6080       *
6081       * @param mixed $data
6082       * @return string empty string or error message
6083       */
6084      function write_setting($data) {
6085          global $CFG;
6086  
6087          $previous = $this->get_setting();
6088          $result = parent::write_setting($data);
6089  
6090          // If saved and the value has changed.
6091          if (empty($result) && $previous != $data) {
6092              require_once($CFG->libdir . '/gradelib.php');
6093              grade_force_site_regrading();
6094          }
6095  
6096          return $result;
6097      }
6098  
6099  }
6100  
6101  
6102  /**
6103   * Primary grade export plugin - has state tracking.
6104   *
6105   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6106   */
6107  class admin_setting_special_gradeexport extends admin_setting_configmulticheckbox {
6108      /**
6109       * Calls parent::__construct with specific arguments
6110       */
6111      public function __construct() {
6112          parent::__construct('gradeexport', get_string('gradeexport', 'admin'),
6113              get_string('configgradeexport', 'admin'), array(), NULL);
6114      }
6115  
6116      /**
6117       * Load the available choices for the multicheckbox
6118       *
6119       * @return bool always returns true
6120       */
6121      public function load_choices() {
6122          if (is_array($this->choices)) {
6123              return true;
6124          }
6125          $this->choices = array();
6126  
6127          if ($plugins = core_component::get_plugin_list('gradeexport')) {
6128              foreach($plugins as $plugin => $unused) {
6129                  $this->choices[$plugin] = get_string('pluginname', 'gradeexport_'.$plugin);
6130              }
6131          }
6132          return true;
6133      }
6134  }
6135  
6136  
6137  /**
6138   * A setting for setting the default grade point value. Must be an integer between 1 and $CFG->gradepointmax.
6139   *
6140   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6141   */
6142  class admin_setting_special_gradepointdefault extends admin_setting_configtext {
6143      /**
6144       * Config gradepointmax constructor
6145       *
6146       * @param string $name Overidden by "gradepointmax"
6147       * @param string $visiblename Overridden by "gradepointmax" language string.
6148       * @param string $description Overridden by "gradepointmax_help" language string.
6149       * @param string $defaultsetting Not used, overridden by 100.
6150       * @param mixed $paramtype Overridden by PARAM_INT.
6151       * @param int $size Overridden by 5.
6152       */
6153      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
6154          $name = 'gradepointdefault';
6155          $visiblename = get_string('gradepointdefault', 'grades');
6156          $description = get_string('gradepointdefault_help', 'grades');
6157          $defaultsetting = 100;
6158          $paramtype = PARAM_INT;
6159          $size = 5;
6160          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
6161      }
6162  
6163      /**
6164       * Validate data before storage
6165       * @param string $data The submitted data
6166       * @return bool|string true if ok, string if error found
6167       */
6168      public function validate($data) {
6169          global $CFG;
6170          if (((string)(int)$data === (string)$data && $data > 0 && $data <= $CFG->gradepointmax)) {
6171              return true;
6172          } else {
6173              return get_string('gradepointdefault_validateerror', 'grades');
6174          }
6175      }
6176  }
6177  
6178  
6179  /**
6180   * A setting for setting the maximum grade value. Must be an integer between 1 and 10000.
6181   *
6182   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6183   */
6184  class admin_setting_special_gradepointmax extends admin_setting_configtext {
6185  
6186      /**
6187       * Config gradepointmax constructor
6188       *
6189       * @param string $name Overidden by "gradepointmax"
6190       * @param string $visiblename Overridden by "gradepointmax" language string.
6191       * @param string $description Overridden by "gradepointmax_help" language string.
6192       * @param string $defaultsetting Not used, overridden by 100.
6193       * @param mixed $paramtype Overridden by PARAM_INT.
6194       * @param int $size Overridden by 5.
6195       */
6196      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
6197          $name = 'gradepointmax';
6198          $visiblename = get_string('gradepointmax', 'grades');
6199          $description = get_string('gradepointmax_help', 'grades');
6200          $defaultsetting = 100;
6201          $paramtype = PARAM_INT;
6202          $size = 5;
6203          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
6204      }
6205  
6206      /**
6207       * Save the selected setting
6208       *
6209       * @param string $data The selected site
6210       * @return string empty string or error message
6211       */
6212      public function write_setting($data) {
6213          if ($data === '') {
6214              $data = (int)$this->defaultsetting;
6215          } else {
6216              $data = $data;
6217          }
6218          return parent::write_setting($data);
6219      }
6220  
6221      /**
6222       * Validate data before storage
6223       * @param string $data The submitted data
6224       * @return bool|string true if ok, string if error found
6225       */
6226      public function validate($data) {
6227          if (((string)(int)$data === (string)$data && $data > 0 && $data <= 10000)) {
6228              return true;
6229          } else {
6230              return get_string('gradepointmax_validateerror', 'grades');
6231          }
6232      }
6233  
6234      /**
6235       * Return an XHTML string for the setting
6236       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6237       * @param string $query search query to be highlighted
6238       * @return string XHTML to display control
6239       */
6240      public function output_html($data, $query = '') {
6241          global $OUTPUT;
6242  
6243          $default = $this->get_defaultsetting();
6244          $context = (object) [
6245              'size' => $this->size,
6246              'id' => $this->get_id(),
6247              'name' => $this->get_full_name(),
6248              'value' => $data,
6249              'attributes' => [
6250                  'maxlength' => 5
6251              ],
6252              'forceltr' => $this->get_force_ltr()
6253          ];
6254          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
6255  
6256          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
6257      }
6258  }
6259  
6260  
6261  /**
6262   * Grade category settings
6263   *
6264   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6265   */
6266  class admin_setting_gradecat_combo extends admin_setting {
6267  
6268      /** @var array Array of choices value=>label. */
6269      public $choices;
6270  
6271      /**
6272       * Sets choices and calls parent::__construct with passed arguments
6273       * @param string $name
6274       * @param string $visiblename
6275       * @param string $description
6276       * @param mixed $defaultsetting string or array depending on implementation
6277       * @param array $choices An array of choices for the control
6278       */
6279      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
6280          $this->choices = $choices;
6281          parent::__construct($name, $visiblename, $description, $defaultsetting);
6282      }
6283  
6284      /**
6285       * Return the current setting(s) array
6286       *
6287       * @return array Array of value=>xx, forced=>xx, adv=>xx
6288       */
6289      public function get_setting() {
6290          global $CFG;
6291  
6292          $value = $this->config_read($this->name);
6293          $flag  = $this->config_read($this->name.'_flag');
6294  
6295          if (is_null($value) or is_null($flag)) {
6296              return NULL;
6297          }
6298  
6299          $flag   = (int)$flag;
6300          $forced = (boolean)(1 & $flag); // first bit
6301          $adv    = (boolean)(2 & $flag); // second bit
6302  
6303          return array('value' => $value, 'forced' => $forced, 'adv' => $adv);
6304      }
6305  
6306      /**
6307       * Save the new settings passed in $data
6308       *
6309       * @todo Add vartype handling to ensure $data is array
6310       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6311       * @return string empty or error message
6312       */
6313      public function write_setting($data) {
6314          global $CFG;
6315  
6316          $value  = $data['value'];
6317          $forced = empty($data['forced']) ? 0 : 1;
6318          $adv    = empty($data['adv'])    ? 0 : 2;
6319          $flag   = ($forced | $adv); //bitwise or
6320  
6321          if (!in_array($value, array_keys($this->choices))) {
6322              return 'Error setting ';
6323          }
6324  
6325          $oldvalue  = $this->config_read($this->name);
6326          $oldflag   = (int)$this->config_read($this->name.'_flag');
6327          $oldforced = (1 & $oldflag); // first bit
6328  
6329          $result1 = $this->config_write($this->name, $value);
6330          $result2 = $this->config_write($this->name.'_flag', $flag);
6331  
6332          // force regrade if needed
6333          if ($oldforced != $forced or ($forced and $value != $oldvalue)) {
6334              require_once($CFG->libdir.'/gradelib.php');
6335              grade_category::updated_forced_settings();
6336          }
6337  
6338          if ($result1 and $result2) {
6339              return '';
6340          } else {
6341              return get_string('errorsetting', 'admin');
6342          }
6343      }
6344  
6345      /**
6346       * Return XHTML to display the field and wrapping div
6347       *
6348       * @todo Add vartype handling to ensure $data is array
6349       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6350       * @param string $query
6351       * @return string XHTML to display control
6352       */
6353      public function output_html($data, $query='') {
6354          global $OUTPUT;
6355  
6356          $value  = $data['value'];
6357  
6358          $default = $this->get_defaultsetting();
6359          if (!is_null($default)) {
6360              $defaultinfo = array();
6361              if (isset($this->choices[$default['value']])) {
6362                  $defaultinfo[] = $this->choices[$default['value']];
6363              }
6364              if (!empty($default['forced'])) {
6365                  $defaultinfo[] = get_string('force');
6366              }
6367              if (!empty($default['adv'])) {
6368                  $defaultinfo[] = get_string('advanced');
6369              }
6370              $defaultinfo = implode(', ', $defaultinfo);
6371  
6372          } else {
6373              $defaultinfo = NULL;
6374          }
6375  
6376          $options = $this->choices;
6377          $context = (object) [
6378              'id' => $this->get_id(),
6379              'name' => $this->get_full_name(),
6380              'forced' => !empty($data['forced']),
6381              'advanced' => !empty($data['adv']),
6382              'options' => array_map(function($option) use ($options, $value) {
6383                  return [
6384                      'value' => $option,
6385                      'name' => $options[$option],
6386                      'selected' => $option == $value
6387                  ];
6388              }, array_keys($options)),
6389          ];
6390  
6391          $element = $OUTPUT->render_from_template('core_admin/setting_gradecat_combo', $context);
6392  
6393          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
6394      }
6395  }
6396  
6397  
6398  /**
6399   * Selection of grade report in user profiles
6400   *
6401   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6402   */
6403  class admin_setting_grade_profilereport extends admin_setting_configselect {
6404      /**
6405       * Calls parent::__construct with specific arguments
6406       */
6407      public function __construct() {
6408          parent::__construct('grade_profilereport', get_string('profilereport', 'grades'), get_string('profilereport_help', 'grades'), 'user', null);
6409      }
6410  
6411      /**
6412       * Loads an array of choices for the configselect control
6413       *
6414       * @return bool always return true
6415       */
6416      public function load_choices() {
6417          if (is_array($this->choices)) {
6418              return true;
6419          }
6420          $this->choices = array();
6421  
6422          global $CFG;
6423          require_once($CFG->libdir.'/gradelib.php');
6424  
6425          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6426              if (file_exists($plugindir.'/lib.php')) {
6427                  require_once($plugindir.'/lib.php');
6428                  $functionname = 'grade_report_'.$plugin.'_profilereport';
6429                  if (function_exists($functionname)) {
6430                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_'.$plugin);
6431                  }
6432              }
6433          }
6434          return true;
6435      }
6436  }
6437  
6438  /**
6439   * Provides a selection of grade reports to be used for "grades".
6440   *
6441   * @copyright 2015 Adrian Greeve <adrian@moodle.com>
6442   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6443   */
6444  class admin_setting_my_grades_report extends admin_setting_configselect {
6445  
6446      /**
6447       * Calls parent::__construct with specific arguments.
6448       */
6449      public function __construct() {
6450          parent::__construct('grade_mygrades_report', new lang_string('mygrades', 'grades'),
6451                  new lang_string('mygrades_desc', 'grades'), 'overview', null);
6452      }
6453  
6454      /**
6455       * Loads an array of choices for the configselect control.
6456       *
6457       * @return bool always returns true.
6458       */
6459      public function load_choices() {
6460          global $CFG; // Remove this line and behold the horror of behat test failures!
6461          $this->choices = array();
6462          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6463              if (file_exists($plugindir . '/lib.php')) {
6464                  require_once($plugindir . '/lib.php');
6465                  // Check to see if the class exists. Check the correct plugin convention first.
6466                  if (class_exists('gradereport_' . $plugin)) {
6467                      $classname = 'gradereport_' . $plugin;
6468                  } else if (class_exists('grade_report_' . $plugin)) {
6469                      // We are using the old plugin naming convention.
6470                      $classname = 'grade_report_' . $plugin;
6471                  } else {
6472                      continue;
6473                  }
6474                  if ($classname::supports_mygrades()) {
6475                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_' . $plugin);
6476                  }
6477              }
6478          }
6479          // Add an option to specify an external url.
6480          $this->choices['external'] = get_string('externalurl', 'grades');
6481          return true;
6482      }
6483  }
6484  
6485  /**
6486   * Special class for register auth selection
6487   *
6488   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6489   */
6490  class admin_setting_special_registerauth extends admin_setting_configselect {
6491      /**
6492       * Calls parent::__construct with specific arguments
6493       */
6494      public function __construct() {
6495          parent::__construct('registerauth', get_string('selfregistration', 'auth'), get_string('selfregistration_help', 'auth'), '', null);
6496      }
6497  
6498      /**
6499       * Returns the default option
6500       *
6501       * @return string empty or default option
6502       */
6503      public function get_defaultsetting() {
6504          $this->load_choices();
6505          $defaultsetting = parent::get_defaultsetting();
6506          if (array_key_exists($defaultsetting, $this->choices)) {
6507              return $defaultsetting;
6508          } else {
6509              return '';
6510          }
6511      }
6512  
6513      /**
6514       * Loads the possible choices for the array
6515       *
6516       * @return bool always returns true
6517       */
6518      public function load_choices() {
6519          global $CFG;
6520  
6521          if (is_array($this->choices)) {
6522              return true;
6523          }
6524          $this->choices = array();
6525          $this->choices[''] = get_string('disable');
6526  
6527          $authsenabled = get_enabled_auth_plugins();
6528  
6529          foreach ($authsenabled as $auth) {
6530              $authplugin = get_auth_plugin($auth);
6531              if (!$authplugin->can_signup()) {
6532                  continue;
6533              }
6534              // Get the auth title (from core or own auth lang files)
6535              $authtitle = $authplugin->get_title();
6536              $this->choices[$auth] = $authtitle;
6537          }
6538          return true;
6539      }
6540  }
6541  
6542  
6543  /**
6544   * General plugins manager
6545   */
6546  class admin_page_pluginsoverview extends admin_externalpage {
6547  
6548      /**
6549       * Sets basic information about the external page
6550       */
6551      public function __construct() {
6552          global $CFG;
6553          parent::__construct('pluginsoverview', get_string('pluginsoverview', 'core_admin'),
6554              "$CFG->wwwroot/$CFG->admin/plugins.php");
6555      }
6556  }
6557  
6558  /**
6559   * Module manage page
6560   *
6561   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6562   */
6563  class admin_page_managemods extends admin_externalpage {
6564      /**
6565       * Calls parent::__construct with specific arguments
6566       */
6567      public function __construct() {
6568          global $CFG;
6569          parent::__construct('managemodules', get_string('modsettings', 'admin'), "$CFG->wwwroot/$CFG->admin/modules.php");
6570      }
6571  
6572      /**
6573       * Try to find the specified module
6574       *
6575       * @param string $query The module to search for
6576       * @return array
6577       */
6578      public function search($query) {
6579          global $CFG, $DB;
6580          if ($result = parent::search($query)) {
6581              return $result;
6582          }
6583  
6584          $found = false;
6585          if ($modules = $DB->get_records('modules')) {
6586              foreach ($modules as $module) {
6587                  if (!file_exists("$CFG->dirroot/mod/$module->name/lib.php")) {
6588                      continue;
6589                  }
6590                  if (strpos($module->name, $query) !== false) {
6591                      $found = true;
6592                      break;
6593                  }
6594                  $strmodulename = get_string('modulename', $module->name);
6595                  if (strpos(core_text::strtolower($strmodulename), $query) !== false) {
6596                      $found = true;
6597                      break;
6598                  }
6599              }
6600          }
6601          if ($found) {
6602              $result = new stdClass();
6603              $result->page     = $this;
6604              $result->settings = array();
6605              return array($this->name => $result);
6606          } else {
6607              return array();
6608          }
6609      }
6610  }
6611  
6612  
6613  /**
6614   * Special class for enrol plugins management.
6615   *
6616   * @copyright 2010 Petr Skoda {@link http://skodak.org}
6617   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6618   */
6619  class admin_setting_manageenrols extends admin_setting {
6620      /**
6621       * Calls parent::__construct with specific arguments
6622       */
6623      public function __construct() {
6624          $this->nosave = true;
6625          parent::__construct('enrolsui', get_string('manageenrols', 'enrol'), '', '');
6626      }
6627  
6628      /**
6629       * Always returns true, does nothing
6630       *
6631       * @return true
6632       */
6633      public function get_setting() {
6634          return true;
6635      }
6636  
6637      /**
6638       * Always returns true, does nothing
6639       *
6640       * @return true
6641       */
6642      public function get_defaultsetting() {
6643          return true;
6644      }
6645  
6646      /**
6647       * Always returns '', does not write anything
6648       *
6649       * @return string Always returns ''
6650       */
6651      public function write_setting($data) {
6652      // do not write any setting
6653          return '';
6654      }
6655  
6656      /**
6657       * Checks if $query is one of the available enrol plugins
6658       *
6659       * @param string $query The string to search for
6660       * @return bool Returns true if found, false if not
6661       */
6662      public function is_related($query) {
6663          if (parent::is_related($query)) {
6664              return true;
6665          }
6666  
6667          $query = core_text::strtolower($query);
6668          $enrols = enrol_get_plugins(false);
6669          foreach ($enrols as $name=>$enrol) {
6670              $localised = get_string('pluginname', 'enrol_'.$name);
6671              if (strpos(core_text::strtolower($name), $query) !== false) {
6672                  return true;
6673              }
6674              if (strpos(core_text::strtolower($localised), $query) !== false) {
6675                  return true;
6676              }
6677          }
6678          return false;
6679      }
6680  
6681      /**
6682       * Builds the XHTML to display the control
6683       *
6684       * @param string $data Unused
6685       * @param string $query
6686       * @return string
6687       */
6688      public function output_html($data, $query='') {
6689          global $CFG, $OUTPUT, $DB, $PAGE;
6690  
6691          // Display strings.
6692          $strup        = get_string('up');
6693          $strdown      = get_string('down');
6694          $strsettings  = get_string('settings');
6695          $strenable    = get_string('enable');
6696          $strdisable   = get_string('disable');
6697          $struninstall = get_string('uninstallplugin', 'core_admin');
6698          $strusage     = get_string('enrolusage', 'enrol');
6699          $strversion   = get_string('version');
6700          $strtest      = get_string('testsettings', 'core_enrol');
6701  
6702          $pluginmanager = core_plugin_manager::instance();
6703  
6704          $enrols_available = enrol_get_plugins(false);
6705          $active_enrols    = enrol_get_plugins(true);
6706  
6707          $allenrols = array();
6708          foreach ($active_enrols as $key=>$enrol) {
6709              $allenrols[$key] = true;
6710          }
6711          foreach ($enrols_available as $key=>$enrol) {
6712              $allenrols[$key] = true;
6713          }
6714          // Now find all borked plugins and at least allow then to uninstall.
6715          $condidates = $DB->get_fieldset_sql("SELECT DISTINCT enrol FROM {enrol}");
6716          foreach ($condidates as $candidate) {
6717              if (empty($allenrols[$candidate])) {
6718                  $allenrols[$candidate] = true;
6719              }
6720          }
6721  
6722          $return = $OUTPUT->heading(get_string('actenrolshhdr', 'enrol'), 3, 'main', true);
6723          $return .= $OUTPUT->box_start('generalbox enrolsui');
6724  
6725          $table = new html_table();
6726          $table->head  = array(get_string('name'), $strusage, $strversion, $strenable, $strup.'/'.$strdown, $strsettings, $strtest, $struninstall);
6727          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
6728          $table->id = 'courseenrolmentplugins';
6729          $table->attributes['class'] = 'admintable generaltable';
6730          $table->data  = array();
6731  
6732          // Iterate through enrol plugins and add to the display table.
6733          $updowncount = 1;
6734          $enrolcount = count($active_enrols);
6735          $url = new moodle_url('/admin/enrol.php', array('sesskey'=>sesskey()));
6736          $printed = array();
6737          foreach($allenrols as $enrol => $unused) {
6738              $plugininfo = $pluginmanager->get_plugin_info('enrol_'.$enrol);
6739              $version = get_config('enrol_'.$enrol, 'version');
6740              if ($version === false) {
6741                  $version = '';
6742              }
6743  
6744              if (get_string_manager()->string_exists('pluginname', 'enrol_'.$enrol)) {
6745                  $name = get_string('pluginname', 'enrol_'.$enrol);
6746              } else {
6747                  $name = $enrol;
6748              }
6749              // Usage.
6750              $ci = $DB->count_records('enrol', array('enrol'=>$enrol));
6751              $cp = $DB->count_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($enrol));
6752              $usage = "$ci / $cp";
6753  
6754              // Hide/show links.
6755              $class = '';
6756              if (isset($active_enrols[$enrol])) {
6757                  $aurl = new moodle_url($url, array('action'=>'disable', 'enrol'=>$enrol));
6758                  $hideshow = "<a href=\"$aurl\">";
6759                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
6760                  $enabled = true;
6761                  $displayname = $name;
6762              } else if (isset($enrols_available[$enrol])) {
6763                  $aurl = new moodle_url($url, array('action'=>'enable', 'enrol'=>$enrol));
6764                  $hideshow = "<a href=\"$aurl\">";
6765                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
6766                  $enabled = false;
6767                  $displayname = $name;
6768                  $class = 'dimmed_text';
6769              } else {
6770                  $hideshow = '';
6771                  $enabled = false;
6772                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
6773              }
6774              if ($PAGE->theme->resolve_image_location('icon', 'enrol_' . $name, false)) {
6775                  $icon = $OUTPUT->pix_icon('icon', '', 'enrol_' . $name, array('class' => 'icon pluginicon'));
6776              } else {
6777                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
6778              }
6779  
6780              // Up/down link (only if enrol is enabled).
6781              $updown = '';
6782              if ($enabled) {
6783                  if ($updowncount > 1) {
6784                      $aurl = new moodle_url($url, array('action'=>'up', 'enrol'=>$enrol));
6785                      $updown .= "<a href=\"$aurl\">";
6786                      $updown .= $OUTPUT->pix_icon('t/up', $strup) . '</a>&nbsp;';
6787                  } else {
6788                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6789                  }
6790                  if ($updowncount < $enrolcount) {
6791                      $aurl = new moodle_url($url, array('action'=>'down', 'enrol'=>$enrol));
6792                      $updown .= "<a href=\"$aurl\">";
6793                      $updown .= $OUTPUT->pix_icon('t/down', $strdown) . '</a>&nbsp;';
6794                  } else {
6795                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6796                  }
6797                  ++$updowncount;
6798              }
6799  
6800              // Add settings link.
6801              if (!$version) {
6802                  $settings = '';
6803              } else if ($surl = $plugininfo->get_settings_url()) {
6804                  $settings = html_writer::link($surl, $strsettings);
6805              } else {
6806                  $settings = '';
6807              }
6808  
6809              // Add uninstall info.
6810              $uninstall = '';
6811              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol, 'manage')) {
6812                  $uninstall = html_writer::link($uninstallurl, $struninstall);
6813              }
6814  
6815              $test = '';
6816              if (!empty($enrols_available[$enrol]) and method_exists($enrols_available[$enrol], 'test_settings')) {
6817                  $testsettingsurl = new moodle_url('/enrol/test_settings.php', array('enrol'=>$enrol, 'sesskey'=>sesskey()));
6818                  $test = html_writer::link($testsettingsurl, $strtest);
6819              }
6820  
6821              // Add a row to the table.
6822              $row = new html_table_row(array($icon.$displayname, $usage, $version, $hideshow, $updown, $settings, $test, $uninstall));
6823              if ($class) {
6824                  $row->attributes['class'] = $class;
6825              }
6826              $table->data[] = $row;
6827  
6828              $printed[$enrol] = true;
6829          }
6830  
6831          $return .= html_writer::table($table);
6832          $return .= get_string('configenrolplugins', 'enrol').'<br />'.get_string('tablenosave', 'admin');
6833          $return .= $OUTPUT->box_end();
6834          return highlight($query, $return);
6835      }
6836  }
6837  
6838  
6839  /**
6840   * Blocks manage page
6841   *
6842   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6843   */
6844  class admin_page_manageblocks extends admin_externalpage {
6845      /**
6846       * Calls parent::__construct with specific arguments
6847       */
6848      public function __construct() {
6849          global $CFG;
6850          parent::__construct('manageblocks', get_string('blocksettings', 'admin'), "$CFG->wwwroot/$CFG->admin/blocks.php");
6851      }
6852  
6853      /**
6854       * Search for a specific block
6855       *
6856       * @param string $query The string to search for
6857       * @return array
6858       */
6859      public function search($query) {
6860          global $CFG, $DB;
6861          if ($result = parent::search($query)) {
6862              return $result;
6863          }
6864  
6865          $found = false;
6866          if ($blocks = $DB->get_records('block')) {
6867              foreach ($blocks as $block) {
6868                  if (!file_exists("$CFG->dirroot/blocks/$block->name/")) {
6869                      continue;
6870                  }
6871                  if (strpos($block->name, $query) !== false) {
6872                      $found = true;
6873                      break;
6874                  }
6875                  $strblockname = get_string('pluginname', 'block_'.$block->name);
6876                  if (strpos(core_text::strtolower($strblockname), $query) !== false) {
6877                      $found = true;
6878                      break;
6879                  }
6880              }
6881          }
6882          if ($found) {
6883              $result = new stdClass();
6884              $result->page     = $this;
6885              $result->settings = array();
6886              return array($this->name => $result);
6887          } else {
6888              return array();
6889          }
6890      }
6891  }
6892  
6893  /**
6894   * Message outputs configuration
6895   *
6896   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6897   */
6898  class admin_page_managemessageoutputs extends admin_externalpage {
6899      /**
6900       * Calls parent::__construct with specific arguments
6901       */
6902      public function __construct() {
6903          global $CFG;
6904          parent::__construct('managemessageoutputs',
6905              get_string('defaultmessageoutputs', 'message'),
6906              new moodle_url('/admin/message.php')
6907          );
6908      }
6909  
6910      /**
6911       * Search for a specific message processor
6912       *
6913       * @param string $query The string to search for
6914       * @return array
6915       */
6916      public function search($query) {
6917          global $CFG, $DB;
6918          if ($result = parent::search($query)) {
6919              return $result;
6920          }
6921  
6922          $found = false;
6923          if ($processors = get_message_processors()) {
6924              foreach ($processors as $processor) {
6925                  if (!$processor->available) {
6926                      continue;
6927                  }
6928                  if (strpos($processor->name, $query) !== false) {
6929                      $found = true;
6930                      break;
6931                  }
6932                  $strprocessorname = get_string('pluginname', 'message_'.$processor->name);
6933                  if (strpos(core_text::strtolower($strprocessorname), $query) !== false) {
6934                      $found = true;
6935                      break;
6936                  }
6937              }
6938          }
6939          if ($found) {
6940              $result = new stdClass();
6941              $result->page     = $this;
6942              $result->settings = array();
6943              return array($this->name => $result);
6944          } else {
6945              return array();
6946          }
6947      }
6948  }
6949  
6950  /**
6951   * Manage question behaviours page
6952   *
6953   * @copyright  2011 The Open University
6954   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6955   */
6956  class admin_page_manageqbehaviours extends admin_externalpage {
6957      /**
6958       * Constructor
6959       */
6960      public function __construct() {
6961          global $CFG;
6962          parent::__construct('manageqbehaviours', get_string('manageqbehaviours', 'admin'),
6963                  new moodle_url('/admin/qbehaviours.php'));
6964      }
6965  
6966      /**
6967       * Search question behaviours for the specified string
6968       *
6969       * @param string $query The string to search for in question behaviours
6970       * @return array
6971       */
6972      public function search($query) {
6973          global $CFG;
6974          if ($result = parent::search($query)) {
6975              return $result;
6976          }
6977  
6978          $found = false;
6979          require_once($CFG->dirroot . '/question/engine/lib.php');
6980          foreach (core_component::get_plugin_list('qbehaviour') as $behaviour => $notused) {
6981              if (strpos(core_text::strtolower(question_engine::get_behaviour_name($behaviour)),
6982                      $query) !== false) {
6983                  $found = true;
6984                  break;
6985              }
6986          }
6987          if ($found) {
6988              $result = new stdClass();
6989              $result->page     = $this;
6990              $result->settings = array();
6991              return array($this->name => $result);
6992          } else {
6993              return array();
6994          }
6995      }
6996  }
6997  
6998  
6999  /**
7000   * Question type manage page
7001   *
7002   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7003   */
7004  class admin_page_manageqtypes extends admin_externalpage {
7005      /**
7006       * Calls parent::__construct with specific arguments
7007       */
7008      public function __construct() {
7009          global $CFG;
7010          parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'),
7011                  new moodle_url('/admin/qtypes.php'));
7012      }
7013  
7014      /**
7015       * Search question types for the specified string
7016       *
7017       * @param string $query The string to search for in question types
7018       * @return array
7019       */
7020      public function search($query) {
7021          global $CFG;
7022          if ($result = parent::search($query)) {
7023              return $result;
7024          }
7025  
7026          $found = false;
7027          require_once($CFG->dirroot . '/question/engine/bank.php');
7028          foreach (question_bank::get_all_qtypes() as $qtype) {
7029              if (strpos(core_text::strtolower($qtype->local_name()), $query) !== false) {
7030                  $found = true;
7031                  break;
7032              }
7033          }
7034          if ($found) {
7035              $result = new stdClass();
7036              $result->page     = $this;
7037              $result->settings = array();
7038              return array($this->name => $result);
7039          } else {
7040              return array();
7041          }
7042      }
7043  }
7044  
7045  
7046  class admin_page_manageportfolios extends admin_externalpage {
7047      /**
7048       * Calls parent::__construct with specific arguments
7049       */
7050      public function __construct() {
7051          global $CFG;
7052          parent::__construct('manageportfolios', get_string('manageportfolios', 'portfolio'),
7053                  "$CFG->wwwroot/$CFG->admin/portfolio.php");
7054      }
7055  
7056      /**
7057       * Searches page for the specified string.
7058       * @param string $query The string to search for
7059       * @return bool True if it is found on this page
7060       */
7061      public function search($query) {
7062          global $CFG;
7063          if ($result = parent::search($query)) {
7064              return $result;
7065          }
7066  
7067          $found = false;
7068          $portfolios = core_component::get_plugin_list('portfolio');
7069          foreach ($portfolios as $p => $dir) {
7070              if (strpos($p, $query) !== false) {
7071                  $found = true;
7072                  break;
7073              }
7074          }
7075          if (!$found) {
7076              foreach (portfolio_instances(false, false) as $instance) {
7077                  $title = $instance->get('name');
7078                  if (strpos(core_text::strtolower($title), $query) !== false) {
7079                      $found = true;
7080                      break;
7081                  }
7082              }
7083          }
7084  
7085          if ($found) {
7086              $result = new stdClass();
7087              $result->page     = $this;
7088              $result->settings = array();
7089              return array($this->name => $result);
7090          } else {
7091              return array();
7092          }
7093      }
7094  }
7095  
7096  
7097  class admin_page_managerepositories extends admin_externalpage {
7098      /**
7099       * Calls parent::__construct with specific arguments
7100       */
7101      public function __construct() {
7102          global $CFG;
7103          parent::__construct('managerepositories', get_string('manage',
7104                  'repository'), "$CFG->wwwroot/$CFG->admin/repository.php");
7105      }
7106  
7107      /**
7108       * Searches page for the specified string.
7109       * @param string $query The string to search for
7110       * @return bool True if it is found on this page
7111       */
7112      public function search($query) {
7113          global $CFG;
7114          if ($result = parent::search($query)) {
7115              return $result;
7116          }
7117  
7118          $found = false;
7119          $repositories= core_component::get_plugin_list('repository');
7120          foreach ($repositories as $p => $dir) {
7121              if (strpos($p, $query) !== false) {
7122                  $found = true;
7123                  break;
7124              }
7125          }
7126          if (!$found) {
7127              foreach (repository::get_types() as $instance) {
7128                  $title = $instance->get_typename();
7129                  if (strpos(core_text::strtolower($title), $query) !== false) {
7130                      $found = true;
7131                      break;
7132                  }
7133              }
7134          }
7135  
7136          if ($found) {
7137              $result = new stdClass();
7138              $result->page     = $this;
7139              $result->settings = array();
7140              return array($this->name => $result);
7141          } else {
7142              return array();
7143          }
7144      }
7145  }
7146  
7147  
7148  /**
7149   * Special class for authentication administration.
7150   *
7151   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7152   */
7153  class admin_setting_manageauths extends admin_setting {
7154      /**
7155       * Calls parent::__construct with specific arguments
7156       */
7157      public function __construct() {
7158          $this->nosave = true;
7159          parent::__construct('authsui', get_string('authsettings', 'admin'), '', '');
7160      }
7161  
7162      /**
7163       * Always returns true
7164       *
7165       * @return true
7166       */
7167      public function get_setting() {
7168          return true;
7169      }
7170  
7171      /**
7172       * Always returns true
7173       *
7174       * @return true
7175       */
7176      public function get_defaultsetting() {
7177          return true;
7178      }
7179  
7180      /**
7181       * Always returns '' and doesn't write anything
7182       *
7183       * @return string Always returns ''
7184       */
7185      public function write_setting($data) {
7186      // do not write any setting
7187          return '';
7188      }
7189  
7190      /**
7191       * Search to find if Query is related to auth plugin
7192       *
7193       * @param string $query The string to search for
7194       * @return bool true for related false for not
7195       */
7196      public function is_related($query) {
7197          if (parent::is_related($query)) {
7198              return true;
7199          }
7200  
7201          $authsavailable = core_component::get_plugin_list('auth');
7202          foreach ($authsavailable as $auth => $dir) {
7203              if (strpos($auth, $query) !== false) {
7204                  return true;
7205              }
7206              $authplugin = get_auth_plugin($auth);
7207              $authtitle = $authplugin->get_title();
7208              if (strpos(core_text::strtolower($authtitle), $query) !== false) {
7209                  return true;
7210              }
7211          }
7212          return false;
7213      }
7214  
7215      /**
7216       * Return XHTML to display control
7217       *
7218       * @param mixed $data Unused
7219       * @param string $query
7220       * @return string highlight
7221       */
7222      public function output_html($data, $query='') {
7223          global $CFG, $OUTPUT, $DB;
7224  
7225          // display strings
7226          $txt = get_strings(array('authenticationplugins', 'users', 'administration',
7227              'settings', 'edit', 'name', 'enable', 'disable',
7228              'up', 'down', 'none', 'users'));
7229          $txt->updown = "$txt->up/$txt->down";
7230          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7231          $txt->testsettings = get_string('testsettings', 'core_auth');
7232  
7233          $authsavailable = core_component::get_plugin_list('auth');
7234          get_enabled_auth_plugins(true); // fix the list of enabled auths
7235          if (empty($CFG->auth)) {
7236              $authsenabled = array();
7237          } else {
7238              $authsenabled = explode(',', $CFG->auth);
7239          }
7240  
7241          // construct the display array, with enabled auth plugins at the top, in order
7242          $displayauths = array();
7243          $registrationauths = array();
7244          $registrationauths[''] = $txt->disable;
7245          $authplugins = array();
7246          foreach ($authsenabled as $auth) {
7247              $authplugin = get_auth_plugin($auth);
7248              $authplugins[$auth] = $authplugin;
7249              /// Get the auth title (from core or own auth lang files)
7250              $authtitle = $authplugin->get_title();
7251              /// Apply titles
7252              $displayauths[$auth] = $authtitle;
7253              if ($authplugin->can_signup()) {
7254                  $registrationauths[$auth] = $authtitle;
7255              }
7256          }
7257  
7258          foreach ($authsavailable as $auth => $dir) {
7259              if (array_key_exists($auth, $displayauths)) {
7260                  continue; //already in the list
7261              }
7262              $authplugin = get_auth_plugin($auth);
7263              $authplugins[$auth] = $authplugin;
7264              /// Get the auth title (from core or own auth lang files)
7265              $authtitle = $authplugin->get_title();
7266              /// Apply titles
7267              $displayauths[$auth] = $authtitle;
7268              if ($authplugin->can_signup()) {
7269                  $registrationauths[$auth] = $authtitle;
7270              }
7271          }
7272  
7273          $return = $OUTPUT->heading(get_string('actauthhdr', 'auth'), 3, 'main');
7274          $return .= $OUTPUT->box_start('generalbox authsui');
7275  
7276          $table = new html_table();
7277          $table->head  = array($txt->name, $txt->users, $txt->enable, $txt->updown, $txt->settings, $txt->testsettings, $txt->uninstall);
7278          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7279          $table->data  = array();
7280          $table->attributes['class'] = 'admintable generaltable';
7281          $table->id = 'manageauthtable';
7282  
7283          //add always enabled plugins first
7284          $displayname = $displayauths['manual'];
7285          $settings = "<a href=\"settings.php?section=authsettingmanual\">{$txt->settings}</a>";
7286          $usercount = $DB->count_records('user', array('auth'=>'manual', 'deleted'=>0));
7287          $table->data[] = array($displayname, $usercount, '', '', $settings, '', '');
7288          $displayname = $displayauths['nologin'];
7289          $usercount = $DB->count_records('user', array('auth'=>'nologin', 'deleted'=>0));
7290          $table->data[] = array($displayname, $usercount, '', '', '', '', '');
7291  
7292  
7293          // iterate through auth plugins and add to the display table
7294          $updowncount = 1;
7295          $authcount = count($authsenabled);
7296          $url = "auth.php?sesskey=" . sesskey();
7297          foreach ($displayauths as $auth => $name) {
7298              if ($auth == 'manual' or $auth == 'nologin') {
7299                  continue;
7300              }
7301              $class = '';
7302              // hide/show link
7303              if (in_array($auth, $authsenabled)) {
7304                  $hideshow = "<a href=\"$url&amp;action=disable&amp;auth=$auth\">";
7305                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7306                  $enabled = true;
7307                  $displayname = $name;
7308              }
7309              else {
7310                  $hideshow = "<a href=\"$url&amp;action=enable&amp;auth=$auth\">";
7311                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7312                  $enabled = false;
7313                  $displayname = $name;
7314                  $class = 'dimmed_text';
7315              }
7316  
7317              $usercount = $DB->count_records('user', array('auth'=>$auth, 'deleted'=>0));
7318  
7319              // up/down link (only if auth is enabled)
7320              $updown = '';
7321              if ($enabled) {
7322                  if ($updowncount > 1) {
7323                      $updown .= "<a href=\"$url&amp;action=up&amp;auth=$auth\">";
7324                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7325                  }
7326                  else {
7327                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7328                  }
7329                  if ($updowncount < $authcount) {
7330                      $updown .= "<a href=\"$url&amp;action=down&amp;auth=$auth\">";
7331                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7332                  }
7333                  else {
7334                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7335                  }
7336                  ++ $updowncount;
7337              }
7338  
7339              // settings link
7340              if (file_exists($CFG->dirroot.'/auth/'.$auth.'/settings.php')) {
7341                  $settings = "<a href=\"settings.php?section=authsetting$auth\">{$txt->settings}</a>";
7342              } else if (file_exists($CFG->dirroot.'/auth/'.$auth.'/config.html')) {
7343                  throw new \coding_exception('config.html is no longer supported, please use settings.php instead.');
7344              } else {
7345                  $settings = '';
7346              }
7347  
7348              // Uninstall link.
7349              $uninstall = '';
7350              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('auth_'.$auth, 'manage')) {
7351                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7352              }
7353  
7354              $test = '';
7355              if (!empty($authplugins[$auth]) and method_exists($authplugins[$auth], 'test_settings')) {
7356                  $testurl = new moodle_url('/auth/test_settings.php', array('auth'=>$auth, 'sesskey'=>sesskey()));
7357                  $test = html_writer::link($testurl, $txt->testsettings);
7358              }
7359  
7360              // Add a row to the table.
7361              $row = new html_table_row(array($displayname, $usercount, $hideshow, $updown, $settings, $test, $uninstall));
7362              if ($class) {
7363                  $row->attributes['class'] = $class;
7364              }
7365              $table->data[] = $row;
7366          }
7367          $return .= html_writer::table($table);
7368          $return .= get_string('configauthenticationplugins', 'admin').'<br />'.get_string('tablenosave', 'filters');
7369          $return .= $OUTPUT->box_end();
7370          return highlight($query, $return);
7371      }
7372  }
7373  
7374  /**
7375   * Special class for antiviruses administration.
7376   *
7377   * @copyright  2015 Ruslan Kabalin, Lancaster University.
7378   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7379   */
7380  class admin_setting_manageantiviruses extends admin_setting {
7381      /**
7382       * Calls parent::__construct with specific arguments
7383       */
7384      public function __construct() {
7385          $this->nosave = true;
7386          parent::__construct('antivirusesui', get_string('antivirussettings', 'antivirus'), '', '');
7387      }
7388  
7389      /**
7390       * Always returns true, does nothing
7391       *
7392       * @return true
7393       */
7394      public function get_setting() {
7395          return true;
7396      }
7397  
7398      /**
7399       * Always returns true, does nothing
7400       *
7401       * @return true
7402       */
7403      public function get_defaultsetting() {
7404          return true;
7405      }
7406  
7407      /**
7408       * Always returns '', does not write anything
7409       *
7410       * @param string $data Unused
7411       * @return string Always returns ''
7412       */
7413      public function write_setting($data) {
7414          // Do not write any setting.
7415          return '';
7416      }
7417  
7418      /**
7419       * Checks if $query is one of the available editors
7420       *
7421       * @param string $query The string to search for
7422       * @return bool Returns true if found, false if not
7423       */
7424      public function is_related($query) {
7425          if (parent::is_related($query)) {
7426              return true;
7427          }
7428  
7429          $antivirusesavailable = \core\antivirus\manager::get_available();
7430          foreach ($antivirusesavailable as $antivirus => $antivirusstr) {
7431              if (strpos($antivirus, $query) !== false) {
7432                  return true;
7433              }
7434              if (strpos(core_text::strtolower($antivirusstr), $query) !== false) {
7435                  return true;
7436              }
7437          }
7438          return false;
7439      }
7440  
7441      /**
7442       * Builds the XHTML to display the control
7443       *
7444       * @param string $data Unused
7445       * @param string $query
7446       * @return string
7447       */
7448      public function output_html($data, $query='') {
7449          global $CFG, $OUTPUT;
7450  
7451          // Display strings.
7452          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7453              'up', 'down', 'none'));
7454          $struninstall = get_string('uninstallplugin', 'core_admin');
7455  
7456          $txt->updown = "$txt->up/$txt->down";
7457  
7458          $antivirusesavailable = \core\antivirus\manager::get_available();
7459          $activeantiviruses = explode(',', $CFG->antiviruses);
7460  
7461          $activeantiviruses = array_reverse($activeantiviruses);
7462          foreach ($activeantiviruses as $key => $antivirus) {
7463              if (empty($antivirusesavailable[$antivirus])) {
7464                  unset($activeantiviruses[$key]);
7465              } else {
7466                  $name = $antivirusesavailable[$antivirus];
7467                  unset($antivirusesavailable[$antivirus]);
7468                  $antivirusesavailable[$antivirus] = $name;
7469              }
7470          }
7471          $antivirusesavailable = array_reverse($antivirusesavailable, true);
7472          $return = $OUTPUT->heading(get_string('actantivirushdr', 'antivirus'), 3, 'main', true);
7473          $return .= $OUTPUT->box_start('generalbox antivirusesui');
7474  
7475          $table = new html_table();
7476          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7477          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7478          $table->id = 'antivirusmanagement';
7479          $table->attributes['class'] = 'admintable generaltable';
7480          $table->data  = array();
7481  
7482          // Iterate through auth plugins and add to the display table.
7483          $updowncount = 1;
7484          $antiviruscount = count($activeantiviruses);
7485          $baseurl = new moodle_url('/admin/antiviruses.php', array('sesskey' => sesskey()));
7486          foreach ($antivirusesavailable as $antivirus => $name) {
7487              // Hide/show link.
7488              $class = '';
7489              if (in_array($antivirus, $activeantiviruses)) {
7490                  $hideshowurl = $baseurl;
7491                  $hideshowurl->params(array('action' => 'disable', 'antivirus' => $antivirus));
7492                  $hideshowimg = $OUTPUT->pix_icon('t/hide', get_string('disable'));
7493                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7494                  $enabled = true;
7495                  $displayname = $name;
7496              } else {
7497                  $hideshowurl = $baseurl;
7498                  $hideshowurl->params(array('action' => 'enable', 'antivirus' => $antivirus));
7499                  $hideshowimg = $OUTPUT->pix_icon('t/show', get_string('enable'));
7500                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7501                  $enabled = false;
7502                  $displayname = $name;
7503                  $class = 'dimmed_text';
7504              }
7505  
7506              // Up/down link.
7507              $updown = '';
7508              if ($enabled) {
7509                  if ($updowncount > 1) {
7510                      $updownurl = $baseurl;
7511                      $updownurl->params(array('action' => 'up', 'antivirus' => $antivirus));
7512                      $updownimg = $OUTPUT->pix_icon('t/up', get_string('moveup'));
7513                      $updown = html_writer::link($updownurl, $updownimg);
7514                  } else {
7515                      $updownimg = $OUTPUT->spacer();
7516                  }
7517                  if ($updowncount < $antiviruscount) {
7518                      $updownurl = $baseurl;
7519                      $updownurl->params(array('action' => 'down', 'antivirus' => $antivirus));
7520                      $updownimg = $OUTPUT->pix_icon('t/down', get_string('movedown'));
7521                      $updown = html_writer::link($updownurl, $updownimg);
7522                  } else {
7523                      $updownimg = $OUTPUT->spacer();
7524                  }
7525                  ++ $updowncount;
7526              }
7527  
7528              // Settings link.
7529              if (file_exists($CFG->dirroot.'/lib/antivirus/'.$antivirus.'/settings.php')) {
7530                  $eurl = new moodle_url('/admin/settings.php', array('section' => 'antivirussettings'.$antivirus));
7531                  $settings = html_writer::link($eurl, $txt->settings);
7532              } else {
7533                  $settings = '';
7534              }
7535  
7536              $uninstall = '';
7537              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('antivirus_'.$antivirus, 'manage')) {
7538                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7539              }
7540  
7541              // Add a row to the table.
7542              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7543              if ($class) {
7544                  $row->attributes['class'] = $class;
7545              }
7546              $table->data[] = $row;
7547          }
7548          $return .= html_writer::table($table);
7549          $return .= get_string('configantivirusplugins', 'antivirus') . html_writer::empty_tag('br') . get_string('tablenosave', 'admin');
7550          $return .= $OUTPUT->box_end();
7551          return highlight($query, $return);
7552      }
7553  }
7554  
7555  /**
7556   * Course formats manager. Allows to enable/disable formats and jump to settings
7557   */
7558  class admin_setting_manageformats extends admin_setting {
7559  
7560      /**
7561       * Calls parent::__construct with specific arguments
7562       */
7563      public function __construct() {
7564          $this->nosave = true;
7565          parent::__construct('formatsui', new lang_string('manageformats', 'core_admin'), '', '');
7566      }
7567  
7568      /**
7569       * Always returns true
7570       *
7571       * @return true
7572       */
7573      public function get_setting() {
7574          return true;
7575      }
7576  
7577      /**
7578       * Always returns true
7579       *
7580       * @return true
7581       */
7582      public function get_defaultsetting() {
7583          return true;
7584      }
7585  
7586      /**
7587       * Always returns '' and doesn't write anything
7588       *
7589       * @param mixed $data string or array, must not be NULL
7590       * @return string Always returns ''
7591       */
7592      public function write_setting($data) {
7593          // do not write any setting
7594          return '';
7595      }
7596  
7597      /**
7598       * Search to find if Query is related to format plugin
7599       *
7600       * @param string $query The string to search for
7601       * @return bool true for related false for not
7602       */
7603      public function is_related($query) {
7604          if (parent::is_related($query)) {
7605              return true;
7606          }
7607          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7608          foreach ($formats as $format) {
7609              if (strpos($format->component, $query) !== false ||
7610                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7611                  return true;
7612              }
7613          }
7614          return false;
7615      }
7616  
7617      /**
7618       * Return XHTML to display control
7619       *
7620       * @param mixed $data Unused
7621       * @param string $query
7622       * @return string highlight
7623       */
7624      public function output_html($data, $query='') {
7625          global $CFG, $OUTPUT;
7626          $return = '';
7627          $return = $OUTPUT->heading(new lang_string('courseformats'), 3, 'main');
7628          $return .= $OUTPUT->box_start('generalbox formatsui');
7629  
7630          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7631  
7632          // display strings
7633          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
7634          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7635          $txt->updown = "$txt->up/$txt->down";
7636  
7637          $table = new html_table();
7638          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
7639          $table->align = array('left', 'center', 'center', 'center', 'center');
7640          $table->attributes['class'] = 'manageformattable generaltable admintable';
7641          $table->data  = array();
7642  
7643          $cnt = 0;
7644          $defaultformat = get_config('moodlecourse', 'format');
7645          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7646          foreach ($formats as $format) {
7647              $url = new moodle_url('/admin/courseformats.php',
7648                      array('sesskey' => sesskey(), 'format' => $format->name));
7649              $isdefault = '';
7650              $class = '';
7651              if ($format->is_enabled()) {
7652                  $strformatname = $format->displayname;
7653                  if ($defaultformat === $format->name) {
7654                      $hideshow = $txt->default;
7655                  } else {
7656                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7657                              $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7658                  }
7659              } else {
7660                  $strformatname = $format->displayname;
7661                  $class = 'dimmed_text';
7662                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7663                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7664              }
7665              $updown = '';
7666              if ($cnt) {
7667                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
7668                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
7669              } else {
7670                  $updown .= $spacer;
7671              }
7672              if ($cnt < count($formats) - 1) {
7673                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
7674                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
7675              } else {
7676                  $updown .= $spacer;
7677              }
7678              $cnt++;
7679              $settings = '';
7680              if ($format->get_settings_url()) {
7681                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
7682              }
7683              $uninstall = '';
7684              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('format_'.$format->name, 'manage')) {
7685                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7686              }
7687              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
7688              if ($class) {
7689                  $row->attributes['class'] = $class;
7690              }
7691              $table->data[] = $row;
7692          }
7693          $return .= html_writer::table($table);
7694          $link = html_writer::link(new moodle_url('/admin/settings.php', array('section' => 'coursesettings')), new lang_string('coursesettings'));
7695          $return .= html_writer::tag('p', get_string('manageformatsgotosettings', 'admin', $link));
7696          $return .= $OUTPUT->box_end();
7697          return highlight($query, $return);
7698      }
7699  }
7700  
7701  /**
7702   * Custom fields manager. Allows to enable/disable custom fields and jump to settings.
7703   *
7704   * @package    core
7705   * @copyright  2018 Toni Barbera
7706   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7707   */
7708  class admin_setting_managecustomfields extends admin_setting {
7709  
7710      /**
7711       * Calls parent::__construct with specific arguments
7712       */
7713      public function __construct() {
7714          $this->nosave = true;
7715          parent::__construct('customfieldsui', new lang_string('managecustomfields', 'core_admin'), '', '');
7716      }
7717  
7718      /**
7719       * Always returns true
7720       *
7721       * @return true
7722       */
7723      public function get_setting() {
7724          return true;
7725      }
7726  
7727      /**
7728       * Always returns true
7729       *
7730       * @return true
7731       */
7732      public function get_defaultsetting() {
7733          return true;
7734      }
7735  
7736      /**
7737       * Always returns '' and doesn't write anything
7738       *
7739       * @param mixed $data string or array, must not be NULL
7740       * @return string Always returns ''
7741       */
7742      public function write_setting($data) {
7743          // Do not write any setting.
7744          return '';
7745      }
7746  
7747      /**
7748       * Search to find if Query is related to format plugin
7749       *
7750       * @param string $query The string to search for
7751       * @return bool true for related false for not
7752       */
7753      public function is_related($query) {
7754          if (parent::is_related($query)) {
7755              return true;
7756          }
7757          $formats = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7758          foreach ($formats as $format) {
7759              if (strpos($format->component, $query) !== false ||
7760                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7761                  return true;
7762              }
7763          }
7764          return false;
7765      }
7766  
7767      /**
7768       * Return XHTML to display control
7769       *
7770       * @param mixed $data Unused
7771       * @param string $query
7772       * @return string highlight
7773       */
7774      public function output_html($data, $query='') {
7775          global $CFG, $OUTPUT;
7776          $return = '';
7777          $return = $OUTPUT->heading(new lang_string('customfields', 'core_customfield'), 3, 'main');
7778          $return .= $OUTPUT->box_start('generalbox customfieldsui');
7779  
7780          $fields = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7781  
7782          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down'));
7783          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7784          $txt->updown = "$txt->up/$txt->down";
7785  
7786          $table = new html_table();
7787          $table->head  = array($txt->name, $txt->enable, $txt->uninstall, $txt->settings);
7788          $table->align = array('left', 'center', 'center', 'center');
7789          $table->attributes['class'] = 'managecustomfieldtable generaltable admintable';
7790          $table->data  = array();
7791  
7792          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7793          foreach ($fields as $field) {
7794              $url = new moodle_url('/admin/customfields.php',
7795                      array('sesskey' => sesskey(), 'field' => $field->name));
7796  
7797              if ($field->is_enabled()) {
7798                  $strfieldname = $field->displayname;
7799                  $class = '';
7800                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7801                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7802              } else {
7803                  $strfieldname = $field->displayname;
7804                  $class = 'dimmed_text';
7805                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7806                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7807              }
7808              $settings = '';
7809              if ($field->get_settings_url()) {
7810                  $settings = html_writer::link($field->get_settings_url(), $txt->settings);
7811              }
7812              $uninstall = '';
7813              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('customfield_'.$field->name, 'manage')) {
7814                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7815              }
7816              $row = new html_table_row(array($strfieldname, $hideshow, $uninstall, $settings));
7817              $row->attributes['class'] = $class;
7818              $table->data[] = $row;
7819          }
7820          $return .= html_writer::table($table);
7821          $return .= $OUTPUT->box_end();
7822          return highlight($query, $return);
7823      }
7824  }
7825  
7826  /**
7827   * Data formats manager. Allow reorder and to enable/disable data formats and jump to settings
7828   *
7829   * @copyright  2016 Brendan Heywood (brendan@catalyst-au.net)
7830   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7831   */
7832  class admin_setting_managedataformats extends admin_setting {
7833  
7834      /**
7835       * Calls parent::__construct with specific arguments
7836       */
7837      public function __construct() {
7838          $this->nosave = true;
7839          parent::__construct('managedataformats', new lang_string('managedataformats'), '', '');
7840      }
7841  
7842      /**
7843       * Always returns true
7844       *
7845       * @return true
7846       */
7847      public function get_setting() {
7848          return true;
7849      }
7850  
7851      /**
7852       * Always returns true
7853       *
7854       * @return true
7855       */
7856      public function get_defaultsetting() {
7857          return true;
7858      }
7859  
7860      /**
7861       * Always returns '' and doesn't write anything
7862       *
7863       * @param mixed $data string or array, must not be NULL
7864       * @return string Always returns ''
7865       */
7866      public function write_setting($data) {
7867          // Do not write any setting.
7868          return '';
7869      }
7870  
7871      /**
7872       * Search to find if Query is related to format plugin
7873       *
7874       * @param string $query The string to search for
7875       * @return bool true for related false for not
7876       */
7877      public function is_related($query) {
7878          if (parent::is_related($query)) {
7879              return true;
7880          }
7881          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
7882          foreach ($formats as $format) {
7883              if (strpos($format->component, $query) !== false ||
7884                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7885                  return true;
7886              }
7887          }
7888          return false;
7889      }
7890  
7891      /**
7892       * Return XHTML to display control
7893       *
7894       * @param mixed $data Unused
7895       * @param string $query
7896       * @return string highlight
7897       */
7898      public function output_html($data, $query='') {
7899          global $CFG, $OUTPUT;
7900          $return = '';
7901  
7902          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
7903  
7904          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
7905          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7906          $txt->updown = "$txt->up/$txt->down";
7907  
7908          $table = new html_table();
7909          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
7910          $table->align = array('left', 'center', 'center', 'center', 'center');
7911          $table->attributes['class'] = 'manageformattable generaltable admintable';
7912          $table->data  = array();
7913  
7914          $cnt = 0;
7915          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7916          $totalenabled = 0;
7917          foreach ($formats as $format) {
7918              if ($format->is_enabled() && $format->is_installed_and_upgraded()) {
7919                  $totalenabled++;
7920              }
7921          }
7922          foreach ($formats as $format) {
7923              $status = $format->get_status();
7924              $url = new moodle_url('/admin/dataformats.php',
7925                      array('sesskey' => sesskey(), 'name' => $format->name));
7926  
7927              $class = '';
7928              if ($format->is_enabled()) {
7929                  $strformatname = $format->displayname;
7930                  if ($totalenabled == 1&& $format->is_enabled()) {
7931                      $hideshow = '';
7932                  } else {
7933                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7934                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7935                  }
7936              } else {
7937                  $class = 'dimmed_text';
7938                  $strformatname = $format->displayname;
7939                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7940                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7941              }
7942  
7943              $updown = '';
7944              if ($cnt) {
7945                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
7946                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
7947              } else {
7948                  $updown .= $spacer;
7949              }
7950              if ($cnt < count($formats) - 1) {
7951                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
7952                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
7953              } else {
7954                  $updown .= $spacer;
7955              }
7956  
7957              $uninstall = '';
7958              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
7959                  $uninstall = get_string('status_missing', 'core_plugin');
7960              } else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
7961                  $uninstall = get_string('status_new', 'core_plugin');
7962              } else if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('dataformat_'.$format->name, 'manage')) {
7963                  if ($totalenabled != 1 || !$format->is_enabled()) {
7964                      $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7965                  }
7966              }
7967  
7968              $settings = '';
7969              if ($format->get_settings_url()) {
7970                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
7971              }
7972  
7973              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
7974              if ($class) {
7975                  $row->attributes['class'] = $class;
7976              }
7977              $table->data[] = $row;
7978              $cnt++;
7979          }
7980          $return .= html_writer::table($table);
7981          return highlight($query, $return);
7982      }
7983  }
7984  
7985  /**
7986   * Special class for filter administration.
7987   *
7988   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7989   */
7990  class admin_page_managefilters extends admin_externalpage {
7991      /**
7992       * Calls parent::__construct with specific arguments
7993       */
7994      public function __construct() {
7995          global $CFG;
7996          parent::__construct('managefilters', get_string('filtersettings', 'admin'), "$CFG->wwwroot/$CFG->admin/filters.php");
7997      }
7998  
7999      /**
8000       * Searches all installed filters for specified filter
8001       *
8002       * @param string $query The filter(string) to search for
8003       * @param string $query
8004       */
8005      public function search($query) {
8006          global $CFG;
8007          if ($result = parent::search($query)) {
8008              return $result;
8009          }
8010  
8011          $found = false;
8012          $filternames = filter_get_all_installed();
8013          foreach ($filternames as $path => $strfiltername) {
8014              if (strpos(core_text::strtolower($strfiltername), $query) !== false) {
8015                  $found = true;
8016                  break;
8017              }
8018              if (strpos($path, $query) !== false) {
8019                  $found = true;
8020                  break;
8021              }
8022          }
8023  
8024          if ($found) {
8025              $result = new stdClass;
8026              $result->page = $this;
8027              $result->settings = array();
8028              return array($this->name => $result);
8029          } else {
8030              return array();
8031          }
8032      }
8033  }
8034  
8035  /**
8036   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8037   * Requires a get_rank method on the plugininfo class for sorting.
8038   *
8039   * @copyright 2017 Damyon Wiese
8040   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8041   */
8042  abstract class admin_setting_manage_plugins extends admin_setting {
8043  
8044      /**
8045       * Get the admin settings section name (just a unique string)
8046       *
8047       * @return string
8048       */
8049      public function get_section_name() {
8050          return 'manage' . $this->get_plugin_type() . 'plugins';
8051      }
8052  
8053      /**
8054       * Get the admin settings section title (use get_string).
8055       *
8056       * @return string
8057       */
8058      abstract public function get_section_title();
8059  
8060      /**
8061       * Get the type of plugin to manage.
8062       *
8063       * @return string
8064       */
8065      abstract public function get_plugin_type();
8066  
8067      /**
8068       * Get the name of the second column.
8069       *
8070       * @return string
8071       */
8072      public function get_info_column_name() {
8073          return '';
8074      }
8075  
8076      /**
8077       * Get the type of plugin to manage.
8078       *
8079       * @param plugininfo The plugin info class.
8080       * @return string
8081       */
8082      abstract public function get_info_column($plugininfo);
8083  
8084      /**
8085       * Calls parent::__construct with specific arguments
8086       */
8087      public function __construct() {
8088          $this->nosave = true;
8089          parent::__construct($this->get_section_name(), $this->get_section_title(), '', '');
8090      }
8091  
8092      /**
8093       * Always returns true, does nothing
8094       *
8095       * @return true
8096       */
8097      public function get_setting() {
8098          return true;
8099      }
8100  
8101      /**
8102       * Always returns true, does nothing
8103       *
8104       * @return true
8105       */
8106      public function get_defaultsetting() {
8107          return true;
8108      }
8109  
8110      /**
8111       * Always returns '', does not write anything
8112       *
8113       * @param mixed $data
8114       * @return string Always returns ''
8115       */
8116      public function write_setting($data) {
8117          // Do not write any setting.
8118          return '';
8119      }
8120  
8121      /**
8122       * Checks if $query is one of the available plugins of this type
8123       *
8124       * @param string $query The string to search for
8125       * @return bool Returns true if found, false if not
8126       */
8127      public function is_related($query) {
8128          if (parent::is_related($query)) {
8129              return true;
8130          }
8131  
8132          $query = core_text::strtolower($query);
8133          $plugins = core_plugin_manager::instance()->get_plugins_of_type($this->get_plugin_type());
8134          foreach ($plugins as $name => $plugin) {
8135              $localised = $plugin->displayname;
8136              if (strpos(core_text::strtolower($name), $query) !== false) {
8137                  return true;
8138              }
8139              if (strpos(core_text::strtolower($localised), $query) !== false) {
8140                  return true;
8141              }
8142          }
8143          return false;
8144      }
8145  
8146      /**
8147       * The URL for the management page for this plugintype.
8148       *
8149       * @return moodle_url
8150       */
8151      protected function get_manage_url() {
8152          return new moodle_url('/admin/updatesetting.php');
8153      }
8154  
8155      /**
8156       * Builds the HTML to display the control.
8157       *
8158       * @param string $data Unused
8159       * @param string $query
8160       * @return string
8161       */
8162      public function output_html($data, $query = '') {
8163          global $CFG, $OUTPUT, $DB, $PAGE;
8164  
8165          $context = (object) [
8166              'manageurl' => new moodle_url($this->get_manage_url(), [
8167                      'type' => $this->get_plugin_type(),
8168                      'sesskey' => sesskey(),
8169                  ]),
8170              'infocolumnname' => $this->get_info_column_name(),
8171              'plugins' => [],
8172          ];
8173  
8174          $pluginmanager = core_plugin_manager::instance();
8175          $allplugins = $pluginmanager->get_plugins_of_type($this->get_plugin_type());
8176          $enabled = $pluginmanager->get_enabled_plugins($this->get_plugin_type());
8177          $plugins = array_merge($enabled, $allplugins);
8178          foreach ($plugins as $key => $plugin) {
8179              $pluginlink = new moodle_url($context->manageurl, ['plugin' => $key]);
8180  
8181              $pluginkey = (object) [
8182                  'plugin' => $plugin->displayname,
8183                  'enabled' => $plugin->is_enabled(),
8184                  'togglelink' => '',
8185                  'moveuplink' => '',
8186                  'movedownlink' => '',
8187                  'settingslink' => $plugin->get_settings_url(),
8188                  'uninstalllink' => '',
8189                  'info' => '',
8190              ];
8191  
8192              // Enable/Disable link.
8193              $togglelink = new moodle_url($pluginlink);
8194              if ($plugin->is_enabled()) {
8195                  $toggletarget = false;
8196                  $togglelink->param('action', 'disable');
8197  
8198                  if (count($context->plugins)) {
8199                      // This is not the first plugin.
8200                      $pluginkey->moveuplink = new moodle_url($pluginlink, ['action' => 'up']);
8201                  }
8202  
8203                  if (count($enabled) > count($context->plugins) + 1) {
8204                      // This is not the last plugin.
8205                      $pluginkey->movedownlink = new moodle_url($pluginlink, ['action' => 'down']);
8206                  }
8207  
8208                  $pluginkey->info = $this->get_info_column($plugin);
8209              } else {
8210                  $toggletarget = true;
8211                  $togglelink->param('action', 'enable');
8212              }
8213  
8214              $pluginkey->toggletarget = $toggletarget;
8215              $pluginkey->togglelink = $togglelink;
8216  
8217              $frankenstyle = $plugin->type . '_' . $plugin->name;
8218              if ($uninstalllink = core_plugin_manager::instance()->get_uninstall_url($frankenstyle, 'manage')) {
8219                  // This plugin supports uninstallation.
8220                  $pluginkey->uninstalllink = $uninstalllink;
8221              }
8222  
8223              if (!empty($this->get_info_column_name())) {
8224                  // This plugintype has an info column.
8225                  $pluginkey->info = $this->get_info_column($plugin);
8226              }
8227  
8228              $context->plugins[] = $pluginkey;
8229          }
8230  
8231          $str = $OUTPUT->render_from_template('core_admin/setting_manage_plugins', $context);
8232          return highlight($query, $str);
8233      }
8234  }
8235  
8236  /**
8237   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8238   * Requires a get_rank method on the plugininfo class for sorting.
8239   *
8240   * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
8241  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8242   */
8243  class admin_setting_manage_fileconverter_plugins extends admin_setting_manage_plugins {
8244      public function get_section_title() {
8245          return get_string('type_fileconverter_plural', 'plugin');
8246      }
8247  
8248      public function get_plugin_type() {
8249          return 'fileconverter';
8250      }
8251  
8252      public function get_info_column_name() {
8253          return get_string('supportedconversions', 'plugin');
8254      }
8255  
8256      public function get_info_column($plugininfo) {
8257          return $plugininfo->get_supported_conversions();
8258      }
8259  }
8260  
8261  /**
8262   * Special class for media player plugins management.
8263   *
8264   * @copyright 2016 Marina Glancy
8265   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8266   */
8267  class admin_setting_managemediaplayers extends admin_setting {
8268      /**
8269       * Calls parent::__construct with specific arguments
8270       */
8271      public function __construct() {
8272          $this->nosave = true;
8273          parent::__construct('managemediaplayers', get_string('managemediaplayers', 'media'), '', '');
8274      }
8275  
8276      /**
8277       * Always returns true, does nothing
8278       *
8279       * @return true
8280       */
8281      public function get_setting() {
8282          return true;
8283      }
8284  
8285      /**
8286       * Always returns true, does nothing
8287       *
8288       * @return true
8289       */
8290      public function get_defaultsetting() {
8291          return true;
8292      }
8293  
8294      /**
8295       * Always returns '', does not write anything
8296       *
8297       * @param mixed $data
8298       * @return string Always returns ''
8299       */
8300      public function write_setting($data) {
8301          // Do not write any setting.
8302          return '';
8303      }
8304  
8305      /**
8306       * Checks if $query is one of the available enrol plugins
8307       *
8308       * @param string $query The string to search for
8309       * @return bool Returns true if found, false if not
8310       */
8311      public function is_related($query) {
8312          if (parent::is_related($query)) {
8313              return true;
8314          }
8315  
8316          $query = core_text::strtolower($query);
8317          $plugins = core_plugin_manager::instance()->get_plugins_of_type('media');
8318          foreach ($plugins as $name => $plugin) {
8319              $localised = $plugin->displayname;
8320              if (strpos(core_text::strtolower($name), $query) !== false) {
8321                  return true;
8322              }
8323              if (strpos(core_text::strtolower($localised), $query) !== false) {
8324                  return true;
8325              }
8326          }
8327          return false;
8328      }
8329  
8330      /**
8331       * Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8332       * @return \core\plugininfo\media[]
8333       */
8334      protected function get_sorted_plugins() {
8335          $pluginmanager = core_plugin_manager::instance();
8336  
8337          $plugins = $pluginmanager->get_plugins_of_type('media');
8338          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8339  
8340          // Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8341          \core_collator::asort_objects_by_method($plugins, 'get_rank', \core_collator::SORT_NUMERIC);
8342  
8343          $order = array_values($enabledplugins);
8344          $order = array_merge($order, array_diff(array_reverse(array_keys($plugins)), $order));
8345  
8346          $sortedplugins = array();
8347          foreach ($order as $name) {
8348              $sortedplugins[$name] = $plugins[$name];
8349          }
8350  
8351          return $sortedplugins;
8352      }
8353  
8354      /**
8355       * Builds the XHTML to display the control
8356       *
8357       * @param string $data Unused
8358       * @param string $query
8359       * @return string
8360       */
8361      public function output_html($data, $query='') {
8362          global $CFG, $OUTPUT, $DB, $PAGE;
8363  
8364          // Display strings.
8365          $strup        = get_string('up');
8366          $strdown      = get_string('down');
8367          $strsettings  = get_string('settings');
8368          $strenable    = get_string('enable');
8369          $strdisable   = get_string('disable');
8370          $struninstall = get_string('uninstallplugin', 'core_admin');
8371          $strversion   = get_string('version');
8372          $strname      = get_string('name');
8373          $strsupports  = get_string('supports', 'core_media');
8374  
8375          $pluginmanager = core_plugin_manager::instance();
8376  
8377          $plugins = $this->get_sorted_plugins();
8378          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8379  
8380          $return = $OUTPUT->box_start('generalbox mediaplayersui');
8381  
8382          $table = new html_table();
8383          $table->head  = array($strname, $strsupports, $strversion,
8384              $strenable, $strup.'/'.$strdown, $strsettings, $struninstall);
8385          $table->colclasses = array('leftalign', 'leftalign', 'centeralign',
8386              'centeralign', 'centeralign', 'centeralign', 'centeralign');
8387          $table->id = 'mediaplayerplugins';
8388          $table->attributes['class'] = 'admintable generaltable';
8389          $table->data  = array();
8390  
8391          // Iterate through media plugins and add to the display table.
8392          $updowncount = 1;
8393          $url = new moodle_url('/admin/media.php', array('sesskey' => sesskey()));
8394          $printed = array();
8395          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8396  
8397          $usedextensions = [];
8398          foreach ($plugins as $name => $plugin) {
8399              $url->param('media', $name);
8400              /** @var \core\plugininfo\media $plugininfo */
8401              $plugininfo = $pluginmanager->get_plugin_info('media_'.$name);
8402              $version = $plugininfo->versiondb;
8403              $supports = $plugininfo->supports($usedextensions);
8404  
8405              // Hide/show links.
8406              $class = '';
8407              if (!$plugininfo->is_installed_and_upgraded()) {
8408                  $hideshow = '';
8409                  $enabled = false;
8410                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
8411              } else {
8412                  $enabled = $plugininfo->is_enabled();
8413                  if ($enabled) {
8414                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'disable')),
8415                          $OUTPUT->pix_icon('t/hide', $strdisable, 'moodle', array('class' => 'iconsmall')));
8416                  } else {
8417                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'enable')),
8418                          $OUTPUT->pix_icon('t/show', $strenable, 'moodle', array('class' => 'iconsmall')));
8419                      $class = 'dimmed_text';
8420                  }
8421                  $displayname = $plugin->displayname;
8422                  if (get_string_manager()->string_exists('pluginname_help', 'media_' . $name)) {
8423                      $displayname .= '&nbsp;' . $OUTPUT->help_icon('pluginname', 'media_' . $name);
8424                  }
8425              }
8426              if ($PAGE->theme->resolve_image_location('icon', 'media_' . $name, false)) {
8427                  $icon = $OUTPUT->pix_icon('icon', '', 'media_' . $name, array('class' => 'icon pluginicon'));
8428              } else {
8429                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
8430              }
8431  
8432              // Up/down link (only if enrol is enabled).
8433              $updown = '';
8434              if ($enabled) {
8435                  if ($updowncount > 1) {
8436                      $updown = html_writer::link(new moodle_url($url, array('action' => 'up')),
8437                          $OUTPUT->pix_icon('t/up', $strup, 'moodle', array('class' => 'iconsmall')));
8438                  } else {
8439                      $updown = $spacer;
8440                  }
8441                  if ($updowncount < count($enabledplugins)) {
8442                      $updown .= html_writer::link(new moodle_url($url, array('action' => 'down')),
8443                          $OUTPUT->pix_icon('t/down', $strdown, 'moodle', array('class' => 'iconsmall')));
8444                  } else {
8445                      $updown .= $spacer;
8446                  }
8447                  ++$updowncount;
8448              }
8449  
8450              $uninstall = '';
8451              $status = $plugininfo->get_status();
8452              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
8453                  $uninstall = get_string('status_missing', 'core_plugin') . '<br/>';
8454              }
8455              if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
8456                  $uninstall = get_string('status_new', 'core_plugin');
8457              } else if ($uninstallurl = $pluginmanager->get_uninstall_url('media_'.$name, 'manage')) {
8458                  $uninstall .= html_writer::link($uninstallurl, $struninstall);
8459              }
8460  
8461              $settings = '';
8462              if ($plugininfo->get_settings_url()) {
8463                  $settings = html_writer::link($plugininfo->get_settings_url(), $strsettings);
8464              }
8465  
8466              // Add a row to the table.
8467              $row = new html_table_row(array($icon.$displayname, $supports, $version, $hideshow, $updown, $settings, $uninstall));
8468              if ($class) {
8469                  $row->attributes['class'] = $class;
8470              }
8471              $table->data[] = $row;
8472  
8473              $printed[$name] = true;
8474          }
8475  
8476          $return .= html_writer::table($table);
8477          $return .= $OUTPUT->box_end();
8478          return highlight($query, $return);
8479      }
8480  }
8481  
8482  
8483  /**
8484   * Content bank content types manager. Allow reorder and to enable/disable content bank content types and jump to settings
8485   *
8486   * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
8487   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8488   */
8489  class admin_setting_managecontentbankcontenttypes extends admin_setting {
8490  
8491      /**
8492       * Calls parent::__construct with specific arguments
8493       */
8494      public function __construct() {
8495          $this->nosave = true;
8496          parent::__construct('contentbank', new lang_string('managecontentbanktypes'), '', '');
8497      }
8498  
8499      /**
8500       * Always returns true
8501       *
8502       * @return true
8503       */
8504      public function get_setting() {
8505          return true;
8506      }
8507  
8508      /**
8509       * Always returns true
8510       *
8511       * @return true
8512       */
8513      public function get_defaultsetting() {
8514          return true;
8515      }
8516  
8517      /**
8518       * Always returns '' and doesn't write anything
8519       *
8520       * @param mixed $data string or array, must not be NULL
8521       * @return string Always returns ''
8522       */
8523      public function write_setting($data) {
8524          // Do not write any setting.
8525          return '';
8526      }
8527  
8528      /**
8529       * Search to find if Query is related to content bank plugin
8530       *
8531       * @param string $query The string to search for
8532       * @return bool true for related false for not
8533       */
8534      public function is_related($query) {
8535          if (parent::is_related($query)) {
8536              return true;
8537          }
8538          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8539          foreach ($types as $type) {
8540              if (strpos($type->component, $query) !== false ||
8541                  strpos(core_text::strtolower($type->displayname), $query) !== false) {
8542                  return true;
8543              }
8544          }
8545          return false;
8546      }
8547  
8548      /**
8549       * Return XHTML to display control
8550       *
8551       * @param mixed $data Unused
8552       * @param string $query
8553       * @return string highlight
8554       */
8555      public function output_html($data, $query='') {
8556          global $CFG, $OUTPUT;
8557          $return = '';
8558  
8559          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8560          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'order', 'up', 'down', 'default'));
8561          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
8562  
8563          $table = new html_table();
8564          $table->head  = array($txt->name, $txt->enable, $txt->order, $txt->settings, $txt->uninstall);
8565          $table->align = array('left', 'center', 'center', 'center', 'center');
8566          $table->attributes['class'] = 'managecontentbanktable generaltable admintable';
8567          $table->data  = array();
8568          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8569  
8570          $totalenabled = 0;
8571          $count = 0;
8572          foreach ($types as $type) {
8573              if ($type->is_enabled() && $type->is_installed_and_upgraded()) {
8574                  $totalenabled++;
8575              }
8576          }
8577  
8578          foreach ($types as $type) {
8579              $url = new moodle_url('/admin/contentbank.php',
8580                  array('sesskey' => sesskey(), 'name' => $type->name));
8581  
8582              $class = '';
8583              $strtypename = $type->displayname;
8584              if ($type->is_enabled()) {
8585                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
8586                      $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
8587              } else {
8588                  $class = 'dimmed_text';
8589                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
8590                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
8591              }
8592  
8593              $updown = '';
8594              if ($count) {
8595                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
8596                          $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
8597              } else {
8598                  $updown .= $spacer;
8599              }
8600              if ($count < count($types) - 1) {
8601                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
8602                          $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
8603              } else {
8604                  $updown .= $spacer;
8605              }
8606  
8607              $settings = '';
8608              if ($type->get_settings_url()) {
8609                  $settings = html_writer::link($type->get_settings_url(), $txt->settings);
8610              }
8611  
8612              $uninstall = '';
8613              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('contenttype_'.$type->name, 'manage')) {
8614                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
8615              }
8616  
8617              $row = new html_table_row(array($strtypename, $hideshow, $updown, $settings, $uninstall));
8618              if ($class) {
8619                  $row->attributes['class'] = $class;
8620              }
8621              $table->data[] = $row;
8622              $count++;
8623          }
8624          $return .= html_writer::table($table);
8625          return highlight($query, $return);
8626      }
8627  }
8628  
8629  /**
8630   * Initialise admin page - this function does require login and permission
8631   * checks specified in page definition.
8632   *
8633   * This function must be called on each admin page before other code.
8634   *
8635   * @global moodle_page $PAGE
8636   *
8637   * @param string $section name of page
8638   * @param string $extrabutton extra HTML that is added after the blocks editing on/off button.
8639   * @param array $extraurlparams an array paramname => paramvalue, or parameters that need to be
8640   *      added to the turn blocks editing on/off form, so this page reloads correctly.
8641   * @param string $actualurl if the actual page being viewed is not the normal one for this
8642   *      page (e.g. admin/roles/allow.php, instead of admin/roles/manage.php, you can pass the alternate URL here.
8643   * @param array $options Additional options that can be specified for page setup.
8644   *      pagelayout - This option can be used to set a specific pagelyaout, admin is default.
8645   *      nosearch - Do not display search bar
8646   */
8647  function admin_externalpage_setup($section, $extrabutton = '', array $extraurlparams = null, $actualurl = '', array $options = array()) {
8648      global $CFG, $PAGE, $USER, $SITE, $OUTPUT;
8649  
8650      $PAGE->set_context(null); // hack - set context to something, by default to system context
8651  
8652      $site = get_site();
8653      require_login(null, false);
8654  
8655      if (!empty($options['pagelayout'])) {
8656          // A specific page layout has been requested.
8657          $PAGE->set_pagelayout($options['pagelayout']);
8658      } else if ($section === 'upgradesettings') {
8659          $PAGE->set_pagelayout('maintenance');
8660      } else {
8661          $PAGE->set_pagelayout('admin');
8662      }
8663  
8664      $adminroot = admin_get_root(false, false); // settings not required for external pages
8665      $extpage = $adminroot->locate($section, true);
8666  
8667      $hassiteconfig = has_capability('moodle/site:config', context_system::instance());
8668      if (empty($extpage) or !($extpage instanceof admin_externalpage)) {
8669          // The requested section isn't in the admin tree
8670          // It could be because the user has inadequate capapbilities or because the section doesn't exist
8671          if (!$hassiteconfig) {
8672              // The requested section could depend on a different capability
8673              // but most likely the user has inadequate capabilities
8674              throw new \moodle_exception('accessdenied', 'admin');
8675          } else {
8676              throw new \moodle_exception('sectionerror', 'admin', "$CFG->wwwroot/$CFG->admin/");
8677          }
8678      }
8679  
8680      // this eliminates our need to authenticate on the actual pages
8681      if (!$extpage->check_access()) {
8682          throw new \moodle_exception('accessdenied', 'admin');
8683          die;
8684      }
8685  
8686      navigation_node::require_admin_tree();
8687  
8688      // $PAGE->set_extra_button($extrabutton); TODO
8689  
8690      if (!$actualurl) {
8691          $actualurl = $extpage->url;
8692      }
8693  
8694      $PAGE->set_url($actualurl, $extraurlparams);
8695      if (strpos($PAGE->pagetype, 'admin-') !== 0) {
8696          $PAGE->set_pagetype('admin-' . $PAGE->pagetype);
8697      }
8698  
8699      if (empty($SITE->fullname) || empty($SITE->shortname)) {
8700          // During initial install.
8701          $strinstallation = get_string('installation', 'install');
8702          $strsettings = get_string('settings');
8703          $PAGE->navbar->add($strsettings);
8704          $PAGE->set_title($strinstallation);
8705          $PAGE->set_heading($strinstallation);
8706          $PAGE->set_cacheable(false);
8707          return;
8708      }
8709  
8710      // Locate the current item on the navigation and make it active when found.
8711      $path = $extpage->path;
8712      $node = $PAGE->settingsnav;
8713      while ($node && count($path) > 0) {
8714          $node = $node->get(array_pop($path));
8715      }
8716      if ($node) {
8717          $node->make_active();
8718      }
8719  
8720      // Normal case.
8721      $adminediting = optional_param('adminedit', -1, PARAM_BOOL);
8722      if ($PAGE->user_allowed_editing() && $adminediting != -1) {
8723          $USER->editing = $adminediting;
8724      }
8725  
8726      if ($PAGE->user_allowed_editing() && !$PAGE->theme->haseditswitch) {
8727          if ($PAGE->user_is_editing()) {
8728              $caption = get_string('blockseditoff');
8729              $url = new moodle_url($PAGE->url, array('adminedit'=>'0', 'sesskey'=>sesskey()));
8730          } else {
8731              $caption = get_string('blocksediton');
8732              $url = new moodle_url($PAGE->url, array('adminedit'=>'1', 'sesskey'=>sesskey()));
8733          }
8734          $PAGE->set_button($OUTPUT->single_button($url, $caption, 'get'));
8735      }
8736  
8737      $PAGE->set_title(implode(moodle_page::TITLE_SEPARATOR, $extpage->visiblepath));
8738      $PAGE->set_heading($SITE->fullname);
8739  
8740      if ($hassiteconfig && empty($options['nosearch'])) {
8741          $PAGE->add_header_action($OUTPUT->render_from_template('core_admin/header_search_input', [
8742              'action' => new moodle_url('/admin/search.php'),
8743              'query' => $PAGE->url->get_param('query'),
8744          ]));
8745      }
8746  
8747      // prevent caching in nav block
8748      $PAGE->navigation->clear_cache();
8749  }
8750  
8751  /**
8752   * Returns the reference to admin tree root
8753   *
8754   * @return object admin_root object
8755   */
8756  function admin_get_root($reload=false, $requirefulltree=true) {
8757      global $CFG, $DB, $OUTPUT, $ADMIN;
8758  
8759      if (is_null($ADMIN)) {
8760      // create the admin tree!
8761          $ADMIN = new admin_root($requirefulltree);
8762      }
8763  
8764      if ($reload or ($requirefulltree and !$ADMIN->fulltree)) {
8765          $ADMIN->purge_children($requirefulltree);
8766      }
8767  
8768      if (!$ADMIN->loaded) {
8769      // we process this file first to create categories first and in correct order
8770          require($CFG->dirroot.'/'.$CFG->admin.'/settings/top.php');
8771  
8772          // now we process all other files in admin/settings to build the admin tree
8773          foreach (glob($CFG->dirroot.'/'.$CFG->admin.'/settings/*.php') as $file) {
8774              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/top.php') {
8775                  continue;
8776              }
8777              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php') {
8778              // plugins are loaded last - they may insert pages anywhere
8779                  continue;
8780              }
8781              require($file);
8782          }
8783          require($CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php');
8784  
8785          $ADMIN->loaded = true;
8786      }
8787  
8788      return $ADMIN;
8789  }
8790  
8791  /// settings utility functions
8792  
8793  /**
8794   * This function applies default settings recursively.
8795   *
8796   * Because setting the defaults of some settings can enable other settings,
8797   * this function calls itself repeatedly (max 4 times) until no more new settings are saved.
8798   *
8799   * NOTE: previous "internal" parameters $admindefaultsettings, $settingsoutput were removed in Moodle 4.3.
8800   *
8801   * @param part_of_admin_tree|null $node NULL means apply all settings with repeated recursion
8802   * @param bool $unconditional if true overrides all values with defaults (true for installation, false for CLI upgrade)
8803   * @return array The names and values of the applied setting defaults
8804   */
8805  function admin_apply_default_settings(?part_of_admin_tree $node = null, bool $unconditional = true): array {
8806      if (is_null($node)) {
8807          // This function relies heavily on config cache, so we need to enable in-memory caches if it
8808          // is used during install when normal caching is disabled.
8809          $token = new \core_cache\allow_temporary_caches(); // Value not used intentionally, see its destructor.
8810  
8811          core_plugin_manager::reset_caches();
8812          $root = admin_get_root(true, true);
8813          $saved = admin_apply_default_settings($root, $unconditional);
8814          if (!$saved) {
8815              return [];
8816          }
8817  
8818          for ($i = 1; $i <= 3; $i++) {
8819              core_plugin_manager::reset_caches();
8820              $root = admin_get_root(true, true);
8821              // No need to force defaults in repeated runs.
8822              $moresaved = admin_apply_default_settings($root, false);
8823              if (!$moresaved) {
8824                  // No more setting defaults to save.
8825                  return $saved;
8826              }
8827              $saved += $moresaved;
8828          }
8829  
8830          // We should not get here unless there are some problematic settings.php files.
8831          core_plugin_manager::reset_caches();
8832          return $saved;
8833      }
8834  
8835      // Recursive applying of defaults in admin tree.
8836      $saved = [];
8837      if ($node instanceof admin_category) {
8838          foreach ($node->children as $child) {
8839              if ($child === null) {
8840                  // This should not happen,
8841                  // this is to prevent theoretical infinite loops.
8842                  continue;
8843              }
8844              if ($child instanceof admin_externalpage) {
8845                  continue;
8846              }
8847              $saved += admin_apply_default_settings($child, $unconditional);
8848          }
8849  
8850      } else if ($node instanceof admin_settingpage) {
8851          /** @var admin_setting $setting */
8852          foreach ((array)$node->settings as $setting) {
8853              if ($setting->nosave) {
8854                  // Not a real setting, must be a heading or description.
8855                  continue;
8856              }
8857              if (!$unconditional && !is_null($setting->get_setting())) {
8858                  // Do not override existing defaults.
8859                  continue;
8860              }
8861              $defaultsetting = $setting->get_defaultsetting();
8862              if (is_null($defaultsetting)) {
8863                  // No value yet - default maybe applied after admin user creation or in upgradesettings.
8864                  continue;
8865              }
8866              // This should be unique-enough setting name that matches administration UI.
8867              if ($setting->plugin === null) {
8868                  $settingname = $setting->name;
8869              } else {
8870                  $settingname = $setting->plugin . '/' . $setting->name;
8871              }
8872              // Set the default for this setting.
8873              $error = $setting->write_setting($defaultsetting);
8874              if ($error === '') {
8875                  $setting->write_setting_flags(null);
8876                  if (is_int($defaultsetting) || $defaultsetting instanceof lang_string
8877                      || $defaultsetting instanceof moodle_url) {
8878                      $defaultsetting = (string)$defaultsetting;
8879                  }
8880                  $saved[$settingname] = $defaultsetting;
8881              } else {
8882                  debugging("Error applying default setting '$settingname': " . $error, DEBUG_DEVELOPER);
8883              }
8884          }
8885      }
8886  
8887      return $saved;
8888  }
8889  
8890  /**
8891   * Store changed settings, this function updates the errors variable in $ADMIN
8892   *
8893   * @param object $formdata from form
8894   * @return int number of changed settings
8895   */
8896  function admin_write_settings($formdata) {
8897      global $CFG, $SITE, $DB;
8898  
8899      $olddbsessions = !empty($CFG->dbsessions);
8900      $formdata = (array)$formdata;
8901  
8902      $data = array();
8903      foreach ($formdata as $fullname=>$value) {
8904          if (strpos($fullname, 's_') !== 0) {
8905              continue; // not a config value
8906          }
8907          $data[$fullname] = $value;
8908      }
8909  
8910      $adminroot = admin_get_root();
8911      $settings = admin_find_write_settings($adminroot, $data);
8912  
8913      $count = 0;
8914      foreach ($settings as $fullname=>$setting) {
8915          /** @var $setting admin_setting */
8916          $original = $setting->get_setting();
8917          $error = $setting->write_setting($data[$fullname]);
8918          if ($error !== '') {
8919              $adminroot->errors[$fullname] = new stdClass();
8920              $adminroot->errors[$fullname]->data  = $data[$fullname];
8921              $adminroot->errors[$fullname]->id    = $setting->get_id();
8922              $adminroot->errors[$fullname]->error = $error;
8923          } else {
8924              $setting->write_setting_flags($data);
8925          }
8926          if ($setting->post_write_settings($original)) {
8927              $count++;
8928          }
8929      }
8930  
8931      if ($olddbsessions != !empty($CFG->dbsessions)) {
8932          require_logout();
8933      }
8934  
8935      // Now update $SITE - just update the fields, in case other people have a
8936      // a reference to it (e.g. $PAGE, $COURSE).
8937      $newsite = $DB->get_record('course', array('id'=>$SITE->id));
8938      foreach (get_object_vars($newsite) as $field => $value) {
8939          $SITE->$field = $value;
8940      }
8941  
8942      // now reload all settings - some of them might depend on the changed
8943      admin_get_root(true);
8944      return $count;
8945  }
8946  
8947  /**
8948   * Internal recursive function - finds all settings from submitted form
8949   *
8950   * @param object $node Instance of admin_category, or admin_settingpage
8951   * @param array $data
8952   * @return array
8953   */
8954  function admin_find_write_settings($node, $data) {
8955      $return = array();
8956  
8957      if (empty($data)) {
8958          return $return;
8959      }
8960  
8961      if ($node instanceof admin_category) {
8962          if ($node->check_access()) {
8963              $entries = array_keys($node->children);
8964              foreach ($entries as $entry) {
8965                  $return = array_merge($return, admin_find_write_settings($node->children[$entry], $data));
8966              }
8967          }
8968  
8969      } else if ($node instanceof admin_settingpage) {
8970          if ($node->check_access()) {
8971              foreach ($node->settings as $setting) {
8972                  $fullname = $setting->get_full_name();
8973                  if (array_key_exists($fullname, $data)) {
8974                      $return[$fullname] = $setting;
8975                  }
8976              }
8977          }
8978  
8979      }
8980  
8981      return $return;
8982  }
8983  
8984  /**
8985   * Internal function - prints the search results
8986   *
8987   * @param string $query String to search for
8988   * @return string empty or XHTML
8989   */
8990  function admin_search_settings_html($query) {
8991      global $CFG, $OUTPUT, $PAGE;
8992  
8993      if (core_text::strlen($query) < 2) {
8994          return '';
8995      }
8996      $query = core_text::strtolower($query);
8997  
8998      $adminroot = admin_get_root();
8999      $findings = $adminroot->search($query);
9000      $savebutton = false;
9001  
9002      $tpldata = (object) [
9003          'actionurl' => $PAGE->url->out(false),
9004          'results' => [],
9005          'sesskey' => sesskey(),
9006      ];
9007  
9008      foreach ($findings as $found) {
9009          $page     = $found->page;
9010          $settings = $found->settings;
9011          if ($page->is_hidden()) {
9012          // hidden pages are not displayed in search results
9013              continue;
9014          }
9015  
9016          $heading = highlight($query, $page->visiblename);
9017          $headingurl = null;
9018          if ($page instanceof admin_externalpage) {
9019              $headingurl = new moodle_url($page->url);
9020          } else if ($page instanceof admin_settingpage) {
9021              $headingurl = new moodle_url('/admin/settings.php', ['section' => $page->name]);
9022          } else {
9023              continue;
9024          }
9025  
9026          // Locate the page in the admin root and populate its visiblepath attribute.
9027          $path = array();
9028          $located = $adminroot->locate($page->name, true);
9029          if ($located) {
9030              foreach ($located->visiblepath as $pathitem) {
9031                  array_unshift($path, (string) $pathitem);
9032              }
9033          }
9034  
9035          $sectionsettings = [];
9036          if (!empty($settings)) {
9037              foreach ($settings as $setting) {
9038                  if (empty($setting->nosave)) {
9039                      $savebutton = true;
9040                  }
9041                  $fullname = $setting->get_full_name();
9042                  if (array_key_exists($fullname, $adminroot->errors)) {
9043                      $data = $adminroot->errors[$fullname]->data;
9044                  } else {
9045                      $data = $setting->get_setting();
9046                  // do not use defaults if settings not available - upgradesettings handles the defaults!
9047                  }
9048                  $sectionsettings[] = $setting->output_html($data, $query);
9049              }
9050          }
9051  
9052          $tpldata->results[] = (object) [
9053              'title' => $heading,
9054              'path' => $path,
9055              'url' => $headingurl->out(false),
9056              'settings' => $sectionsettings
9057          ];
9058      }
9059  
9060      $tpldata->showsave = $savebutton;
9061      $tpldata->hasresults = !empty($tpldata->results);
9062  
9063      return $OUTPUT->render_from_template('core_admin/settings_search_results', $tpldata);
9064  }
9065  
9066  /**
9067   * Internal function - returns arrays of html pages with uninitialised settings
9068   *
9069   * @param object $node Instance of admin_category or admin_settingpage
9070   * @return array
9071   */
9072  function admin_output_new_settings_by_page($node) {
9073      global $OUTPUT;
9074      $return = array();
9075  
9076      if ($node instanceof admin_category) {
9077          $entries = array_keys($node->children);
9078          foreach ($entries as $entry) {
9079              $return += admin_output_new_settings_by_page($node->children[$entry]);
9080          }
9081  
9082      } else if ($node instanceof admin_settingpage) {
9083              $newsettings = array();
9084              foreach ($node->settings as $setting) {
9085                  if (is_null($setting->get_setting())) {
9086                      $newsettings[] = $setting;
9087                  }
9088              }
9089              if (count($newsettings) > 0) {
9090                  $adminroot = admin_get_root();
9091                  $page = $OUTPUT->heading(get_string('upgradesettings','admin').' - '.$node->visiblename, 2, 'main');
9092                  $page .= '<fieldset class="adminsettings">'."\n";
9093                  foreach ($newsettings as $setting) {
9094                      $fullname = $setting->get_full_name();
9095                      if (array_key_exists($fullname, $adminroot->errors)) {
9096                          $data = $adminroot->errors[$fullname]->data;
9097                      } else {
9098                          $data = $setting->get_setting();
9099                          if (is_null($data)) {
9100                              $data = $setting->get_defaultsetting();
9101                          }
9102                      }
9103                      $page .= '<div class="clearer"><!-- --></div>'."\n";
9104                      $page .= $setting->output_html($data);
9105                  }
9106                  $page .= '</fieldset>';
9107                  $return[$node->name] = $page;
9108              }
9109          }
9110  
9111      return $return;
9112  }
9113  
9114  /**
9115   * Format admin settings
9116   *
9117   * @param object $setting
9118   * @param string $title label element
9119   * @param string $form form fragment, html code - not highlighted automatically
9120   * @param string $description
9121   * @param mixed $label link label to id, true by default or string being the label to connect it to
9122   * @param string $warning warning text
9123   * @param sting $defaultinfo defaults info, null means nothing, '' is converted to "Empty" string, defaults to null
9124   * @param string $query search query to be highlighted
9125   * @return string XHTML
9126   */
9127  function format_admin_setting($setting, $title='', $form='', $description='', $label=true, $warning='', $defaultinfo=NULL, $query='') {
9128      global $CFG, $OUTPUT;
9129  
9130      $context = (object) [
9131          'name' => empty($setting->plugin) ? $setting->name : "$setting->plugin | $setting->name",
9132          'fullname' => $setting->get_full_name(),
9133      ];
9134  
9135      // Sometimes the id is not id_s_name, but id_s_name_m or something, and this does not validate.
9136      if ($label === true) {
9137          $context->labelfor = $setting->get_id();
9138      } else if ($label === false) {
9139          $context->labelfor = '';
9140      } else {
9141          $context->labelfor = $label;
9142      }
9143  
9144      $form .= $setting->output_setting_flags();
9145  
9146      $context->warning = $warning;
9147      $context->override = '';
9148      if (empty($setting->plugin)) {
9149          if ($setting->is_forceable() && array_key_exists($setting->name, $CFG->config_php_settings)) {
9150              $context->override = get_string('configoverride', 'admin');
9151          }
9152      } else {
9153          if (array_key_exists($setting->plugin, $CFG->forced_plugin_settings) and array_key_exists($setting->name, $CFG->forced_plugin_settings[$setting->plugin])) {
9154              $context->override = get_string('configoverride', 'admin');
9155          }
9156      }
9157  
9158      $defaults = array();
9159      if (!is_null($defaultinfo)) {
9160          if ($defaultinfo === '') {
9161              $defaultinfo = get_string('emptysettingvalue', 'admin');
9162          }
9163          $defaults[] = $defaultinfo;
9164      }
9165  
9166      $context->default = null;
9167      $setting->get_setting_flag_defaults($defaults);
9168      if (!empty($defaults)) {
9169          $defaultinfo = implode(', ', $defaults);
9170          $defaultinfo = highlight($query, nl2br(s($defaultinfo)));
9171          $context->default = get_string('defaultsettinginfo', 'admin', $defaultinfo);
9172      }
9173  
9174  
9175      $context->error = '';
9176      $adminroot = admin_get_root();
9177      if (array_key_exists($context->fullname, $adminroot->errors)) {
9178          $context->error = $adminroot->errors[$context->fullname]->error;
9179      }
9180  
9181      if ($dependenton = $setting->get_dependent_on()) {
9182          $context->dependenton = get_string('settingdependenton', 'admin', implode(', ', $dependenton));
9183      }
9184  
9185      $context->id = 'admin-' . $setting->name;
9186      $context->title = highlightfast($query, $title);
9187      $context->name = highlightfast($query, $context->name);
9188      $context->description = highlight($query, markdown_to_html($description));
9189      $context->element = $form;
9190      $context->forceltr = $setting->get_force_ltr();
9191      $context->customcontrol = $setting->has_custom_form_control();
9192  
9193      return $OUTPUT->render_from_template('core_admin/setting', $context);
9194  }
9195  
9196  /**
9197   * Based on find_new_settings{@link ()}  in upgradesettings.php
9198   * Looks to find any admin settings that have not been initialized. Returns 1 if it finds any.
9199   *
9200   * @param object $node Instance of admin_category, or admin_settingpage
9201   * @return boolean true if any settings haven't been initialised, false if they all have
9202   */
9203  function any_new_admin_settings($node) {
9204  
9205      if ($node instanceof admin_category) {
9206          $entries = array_keys($node->children);
9207          foreach ($entries as $entry) {
9208              if (any_new_admin_settings($node->children[$entry])) {
9209                  return true;
9210              }
9211          }
9212  
9213      } else if ($node instanceof admin_settingpage) {
9214              foreach ($node->settings as $setting) {
9215                  if ($setting->get_setting() === NULL) {
9216                      return true;
9217                  }
9218              }
9219          }
9220  
9221      return false;
9222  }
9223  
9224  /**
9225   * Given a table and optionally a column name should replaces be done?
9226   *
9227   * @param string $table name
9228   * @param string $column name
9229   * @return bool success or fail
9230   */
9231  function db_should_replace($table, $column = '', $additionalskiptables = ''): bool {
9232  
9233      // TODO: this is horrible hack, we should have a hook and each plugin should be responsible for proper replacing...
9234      $skiptables = ['config', 'config_plugins', 'filter_config', 'sessions',
9235          'events_queue', 'repository_instance_config', 'block_instances', 'files'];
9236  
9237      // Additional skip tables.
9238      if (!empty($additionalskiptables)) {
9239          $skiptables = array_merge($skiptables, explode(',', str_replace(' ', '',  $additionalskiptables)));
9240      }
9241  
9242      // Don't process these.
9243      if (in_array($table, $skiptables)) {
9244          return false;
9245      }
9246  
9247      // To be safe never replace inside a table that looks related to logging.
9248      if (preg_match('/(^|_)logs?($|_)/', $table)) {
9249          return false;
9250      }
9251  
9252      // Do column based exclusions.
9253      if (!empty($column)) {
9254          // Don't touch anything that looks like a hash.
9255          if (preg_match('/hash$/', $column)) {
9256              return false;
9257          }
9258      }
9259  
9260      return true;
9261  }
9262  
9263  /**
9264   * Moved from admin/replace.php so that we can use this in cron
9265   *
9266   * @param string $search string to look for
9267   * @param string $replace string to replace
9268   * @return bool success or fail
9269   */
9270  function db_replace($search, $replace, $additionalskiptables = '') {
9271      global $DB, $CFG, $OUTPUT;
9272  
9273      // Turn off time limits, sometimes upgrades can be slow.
9274      core_php_time_limit::raise();
9275  
9276      if (!$tables = $DB->get_tables() ) {    // No tables yet at all.
9277          return false;
9278      }
9279      foreach ($tables as $table) {
9280  
9281          if (!db_should_replace($table, '', $additionalskiptables)) {
9282              continue;
9283          }
9284  
9285          if ($columns = $DB->get_columns($table)) {
9286              $DB->set_debug(true);
9287              foreach ($columns as $column) {
9288                  if (!db_should_replace($table, $column->name)) {
9289                      continue;
9290                  }
9291                  $DB->replace_all_text($table, $column, $search, $replace);
9292              }
9293              $DB->set_debug(false);
9294          }
9295      }
9296  
9297      // delete modinfo caches
9298      rebuild_course_cache(0, true);
9299  
9300      // TODO: we should ask all plugins to do the search&replace, for now let's do only blocks...
9301      $blocks = core_component::get_plugin_list('block');
9302      foreach ($blocks as $blockname=>$fullblock) {
9303          if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
9304              continue;
9305          }
9306  
9307          if (!is_readable($fullblock.'/lib.php')) {
9308              continue;
9309          }
9310  
9311          $function = 'block_'.$blockname.'_global_db_replace';
9312          include_once($fullblock.'/lib.php');
9313          if (!function_exists($function)) {
9314              continue;
9315          }
9316  
9317          echo $OUTPUT->notification("Replacing in $blockname blocks...", 'notifysuccess');
9318          $function($search, $replace);
9319          echo $OUTPUT->notification("...finished", 'notifysuccess');
9320      }
9321  
9322      // Trigger an event.
9323      $eventargs = [
9324          'context' => context_system::instance(),
9325          'other' => [
9326              'search' => $search,
9327              'replace' => $replace
9328          ]
9329      ];
9330      $event = \core\event\database_text_field_content_replaced::create($eventargs);
9331      $event->trigger();
9332  
9333      purge_all_caches();
9334  
9335      return true;
9336  }
9337  
9338  /**
9339   * Manage repository settings
9340   *
9341   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
9342   */
9343  class admin_setting_managerepository extends admin_setting {
9344  /** @var string */
9345      private $baseurl;
9346  
9347      /**
9348       * calls parent::__construct with specific arguments
9349       */
9350      public function __construct() {
9351          global $CFG;
9352          parent::__construct('managerepository', get_string('manage', 'repository'), '', '');
9353          $this->baseurl = $CFG->wwwroot . '/' . $CFG->admin . '/repository.php?sesskey=' . sesskey();
9354      }
9355  
9356      /**
9357       * Always returns true, does nothing
9358       *
9359       * @return true
9360       */
9361      public function get_setting() {
9362          return true;
9363      }
9364  
9365      /**
9366       * Always returns true does nothing
9367       *
9368       * @return true
9369       */
9370      public function get_defaultsetting() {
9371          return true;
9372      }
9373  
9374      /**
9375       * Always returns s_managerepository
9376       *
9377       * @return string Always return 's_managerepository'
9378       */
9379      public function get_full_name() {
9380          return 's_managerepository';
9381      }
9382  
9383      /**
9384       * Always returns '' doesn't do anything
9385       */
9386      public function write_setting($data) {
9387          $url = $this->baseurl . '&amp;new=' . $data;
9388          return '';
9389      // TODO
9390      // Should not use redirect and exit here
9391      // Find a better way to do this.
9392      // redirect($url);
9393      // exit;
9394      }
9395  
9396      /**
9397       * Searches repository plugins for one that matches $query
9398       *
9399       * @param string $query The string to search for
9400       * @return bool true if found, false if not
9401       */
9402      public function is_related($query) {
9403          if (parent::is_related($query)) {
9404              return true;
9405          }
9406  
9407          $repositories= core_component::get_plugin_list('repository');
9408          foreach ($repositories as $p => $dir) {
9409              if (strpos($p, $query) !== false) {
9410                  return true;
9411              }
9412          }
9413          foreach (repository::get_types() as $instance) {
9414              $title = $instance->get_typename();
9415              if (strpos(core_text::strtolower($title), $query) !== false) {
9416                  return true;
9417              }
9418          }
9419          return false;
9420      }
9421  
9422      /**
9423       * Helper function that generates a moodle_url object
9424       * relevant to the repository
9425       */
9426  
9427      function repository_action_url($repository) {
9428          return new moodle_url($this->baseurl, array('sesskey'=>sesskey(), 'repos'=>$repository));
9429      }
9430  
9431      /**
9432       * Builds XHTML to display the control
9433       *
9434       * @param string $data Unused
9435       * @param string $query
9436       * @return string XHTML
9437       */
9438      public function output_html($data, $query='') {
9439          global $CFG, $USER, $OUTPUT;
9440  
9441          // Get strings that are used
9442          $strshow = get_string('on', 'repository');
9443          $strhide = get_string('off', 'repository');
9444          $strdelete = get_string('disabled', 'repository');
9445  
9446          $actionchoicesforexisting = array(
9447              'show' => $strshow,
9448              'hide' => $strhide,
9449              'delete' => $strdelete
9450          );
9451  
9452          $actionchoicesfornew = array(
9453              'newon' => $strshow,
9454              'newoff' => $strhide,
9455              'delete' => $strdelete
9456          );
9457  
9458          $return = '';
9459          $return .= $OUTPUT->box_start('generalbox');
9460  
9461          // Set strings that are used multiple times
9462          $settingsstr = get_string('settings');
9463          $disablestr = get_string('disable');
9464  
9465          // Table to list plug-ins
9466          $table = new html_table();
9467          $table->head = array(get_string('name'), get_string('isactive', 'repository'), get_string('order'), $settingsstr);
9468          $table->align = array('left', 'center', 'center', 'center', 'center');
9469          $table->data = array();
9470  
9471          // Get list of used plug-ins
9472          $repositorytypes = repository::get_types();
9473          if (!empty($repositorytypes)) {
9474              // Array to store plugins being used
9475              $alreadyplugins = array();
9476              $totalrepositorytypes = count($repositorytypes);
9477              $updowncount = 1;
9478              foreach ($repositorytypes as $i) {
9479                  $settings = '';
9480                  $typename = $i->get_typename();
9481                  // Display edit link only if you can config the type or if it has multiple instances (e.g. has instance config)
9482                  $typeoptionnames = repository::static_function($typename, 'get_type_option_names');
9483                  $instanceoptionnames = repository::static_function($typename, 'get_instance_option_names');
9484  
9485                  if (!empty($typeoptionnames) || !empty($instanceoptionnames)) {
9486                      // Calculate number of instances in order to display them for the Moodle administrator
9487                      if (!empty($instanceoptionnames)) {
9488                          $params = array();
9489                          $params['context'] = array(context_system::instance());
9490                          $params['onlyvisible'] = false;
9491                          $params['type'] = $typename;
9492                          $admininstancenumber = count(repository::static_function($typename, 'get_instances', $params));
9493                          // site instances
9494                          $admininstancenumbertext = get_string('instancesforsite', 'repository', $admininstancenumber);
9495                          $params['context'] = array();
9496                          $instances = repository::static_function($typename, 'get_instances', $params);
9497                          $courseinstances = array();
9498                          $userinstances = array();
9499  
9500                          foreach ($instances as $instance) {
9501                              $repocontext = context::instance_by_id($instance->instance->contextid);
9502                              if ($repocontext->contextlevel == CONTEXT_COURSE) {
9503                                  $courseinstances[] = $instance;
9504                              } else if ($repocontext->contextlevel == CONTEXT_USER) {
9505                                  $userinstances[] = $instance;
9506                              }
9507                          }
9508                          // course instances
9509                          $instancenumber = count($courseinstances);
9510                          $courseinstancenumbertext = get_string('instancesforcourses', 'repository', $instancenumber);
9511  
9512                          // user private instances
9513                          $instancenumber =  count($userinstances);
9514                          $userinstancenumbertext = get_string('instancesforusers', 'repository', $instancenumber);
9515                      } else {
9516                          $admininstancenumbertext = "";
9517                          $courseinstancenumbertext = "";
9518                          $userinstancenumbertext = "";
9519                      }
9520  
9521                      $settings .= '<a href="' . $this->baseurl . '&amp;action=edit&amp;repos=' . $typename . '">' . $settingsstr .'</a>';
9522  
9523                      $settings .= $OUTPUT->container_start('mdl-left');
9524                      $settings .= '<br/>';
9525                      $settings .= $admininstancenumbertext;
9526                      $settings .= '<br/>';
9527                      $settings .= $courseinstancenumbertext;
9528                      $settings .= '<br/>';
9529                      $settings .= $userinstancenumbertext;
9530                      $settings .= $OUTPUT->container_end();
9531                  }
9532                  // Get the current visibility
9533                  if ($i->get_visible()) {
9534                      $currentaction = 'show';
9535                  } else {
9536                      $currentaction = 'hide';
9537                  }
9538  
9539                  $select = new single_select($this->repository_action_url($typename, 'repos'), 'action', $actionchoicesforexisting, $currentaction, null, 'applyto' . basename($typename));
9540  
9541                  // Display up/down link
9542                  $updown = '';
9543                  // Should be done with CSS instead.
9544                  $spacer = $OUTPUT->spacer(array('height' => 15, 'width' => 15, 'class' => 'smallicon'));
9545  
9546                  if ($updowncount > 1) {
9547                      $updown .= "<a href=\"$this->baseurl&amp;action=moveup&amp;repos=".$typename."\">";
9548                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
9549                  }
9550                  else {
9551                      $updown .= $spacer;
9552                  }
9553                  if ($updowncount < $totalrepositorytypes) {
9554                      $updown .= "<a href=\"$this->baseurl&amp;action=movedown&amp;repos=".$typename."\">";
9555                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
9556                  }
9557                  else {
9558                      $updown .= $spacer;
9559                  }
9560  
9561                  $updowncount++;
9562  
9563                  $table->data[] = array($i->get_readablename(), $OUTPUT->render($select), $updown, $settings);
9564  
9565                  if (!in_array($typename, $alreadyplugins)) {
9566                      $alreadyplugins[] = $typename;
9567                  }
9568              }
9569          }
9570  
9571          // Get all the plugins that exist on disk
9572          $plugins = core_component::get_plugin_list('repository');
9573          if (!empty($plugins)) {
9574              foreach ($plugins as $plugin => $dir) {
9575                  // Check that it has not already been listed
9576                  if (!in_array($plugin, $alreadyplugins)) {
9577                      $select = new single_select($this->repository_action_url($plugin, 'repos'), 'action', $actionchoicesfornew, 'delete', null, 'applyto' . basename($plugin));
9578                      $table->data[] = array(get_string('pluginname', 'repository_'.$plugin), $OUTPUT->render($select), '', '');
9579                  }
9580              }
9581          }
9582  
9583          $return .= html_writer::table($table);
9584          $return .= $OUTPUT->box_end();
9585          return highlight($query, $return);
9586      }
9587  }
9588  
9589  /**
9590   * Special checkbox for enable mobile web service
9591   * If enable then we store the service id of the mobile service into config table
9592   * If disable then we unstore the service id from the config table
9593   */
9594  class admin_setting_enablemobileservice extends admin_setting_configcheckbox {
9595  
9596      /** @var boolean True means that the capability 'webservice/rest:use' is set for authenticated user role */
9597      private $restuse;
9598  
9599      /**
9600       * Return true if Authenticated user role has the capability 'webservice/rest:use', otherwise false.
9601       *
9602       * @return boolean
9603       */
9604      private function is_protocol_cap_allowed() {
9605          global $DB, $CFG;
9606  
9607          // If the $this->restuse variable is not set, it needs to be set.
9608          if (empty($this->restuse) and $this->restuse!==false) {
9609              $params = array();
9610              $params['permission'] = CAP_ALLOW;
9611              $params['roleid'] = $CFG->defaultuserroleid;
9612              $params['capability'] = 'webservice/rest:use';
9613              $this->restuse = $DB->record_exists('role_capabilities', $params);
9614          }
9615  
9616          return $this->restuse;
9617      }
9618  
9619      /**
9620       * Set the 'webservice/rest:use' to the Authenticated user role (allow or not)
9621       * @param type $status true to allow, false to not set
9622       */
9623      private function set_protocol_cap($status) {
9624          global $CFG;
9625          if ($status and !$this->is_protocol_cap_allowed()) {
9626              //need to allow the cap
9627              $permission = CAP_ALLOW;
9628              $assign = true;
9629          } else if (!$status and $this->is_protocol_cap_allowed()){
9630              //need to disallow the cap
9631              $permission = CAP_INHERIT;
9632              $assign = true;
9633          }
9634          if (!empty($assign)) {
9635              $systemcontext = context_system::instance();
9636              assign_capability('webservice/rest:use', $permission, $CFG->defaultuserroleid, $systemcontext->id, true);
9637          }
9638      }
9639  
9640      /**
9641       * Builds XHTML to display the control.
9642       * The main purpose of this overloading is to display a warning when https
9643       * is not supported by the server
9644       * @param string $data Unused
9645       * @param string $query
9646       * @return string XHTML
9647       */
9648      public function output_html($data, $query='') {
9649          global $OUTPUT;
9650          $html = parent::output_html($data, $query);
9651  
9652          if ((string)$data === $this->yes) {
9653              $notifications = tool_mobile\api::get_potential_config_issues(); // Safe to call, plugin available if we reach here.
9654              foreach ($notifications as $notification) {
9655                  $message = get_string($notification[0], $notification[1]);
9656                  $html .= $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING);
9657              }
9658          }
9659  
9660          return $html;
9661      }
9662  
9663      /**
9664       * Retrieves the current setting using the objects name
9665       *
9666       * @return string
9667       */
9668      public function get_setting() {
9669          global $CFG;
9670  
9671          // First check if is not set.
9672          $result = $this->config_read($this->name);
9673          if (is_null($result)) {
9674              return null;
9675          }
9676  
9677          // For install cli script, $CFG->defaultuserroleid is not set so return 0
9678          // Or if web services aren't enabled this can't be,
9679          if (empty($CFG->defaultuserroleid) || empty($CFG->enablewebservices)) {
9680              return 0;
9681          }
9682  
9683          require_once($CFG->dirroot . '/webservice/lib.php');
9684          $webservicemanager = new webservice();
9685          $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9686          if ($mobileservice->enabled and $this->is_protocol_cap_allowed()) {
9687              return $result;
9688          } else {
9689              return 0;
9690          }
9691      }
9692  
9693      /**
9694       * Save the selected setting
9695       *
9696       * @param string $data The selected site
9697       * @return string empty string or error message
9698       */
9699      public function write_setting($data) {
9700          global $DB, $CFG;
9701  
9702          //for install cli script, $CFG->defaultuserroleid is not set so do nothing
9703          if (empty($CFG->defaultuserroleid)) {
9704              return '';
9705          }
9706  
9707          $servicename = MOODLE_OFFICIAL_MOBILE_SERVICE;
9708  
9709          require_once($CFG->dirroot . '/webservice/lib.php');
9710          $webservicemanager = new webservice();
9711  
9712          $updateprotocol = false;
9713          if ((string)$data === $this->yes) {
9714               //code run when enable mobile web service
9715               //enable web service systeme if necessary
9716               set_config('enablewebservices', true);
9717  
9718               //enable mobile service
9719               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9720               $mobileservice->enabled = 1;
9721               $webservicemanager->update_external_service($mobileservice);
9722  
9723               // Enable REST server.
9724               $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
9725  
9726               if (!in_array('rest', $activeprotocols)) {
9727                   $activeprotocols[] = 'rest';
9728                   $updateprotocol = true;
9729               }
9730  
9731               if ($updateprotocol) {
9732                   set_config('webserviceprotocols', implode(',', $activeprotocols));
9733               }
9734  
9735               // Allow rest:use capability for authenticated user.
9736               $this->set_protocol_cap(true);
9737           } else {
9738               // Disable the mobile service.
9739               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9740               $mobileservice->enabled = 0;
9741               $webservicemanager->update_external_service($mobileservice);
9742           }
9743  
9744          return (parent::write_setting($data));
9745      }
9746  }
9747  
9748  /**
9749   * Special class for management of external services
9750   *
9751   * @author Petr Skoda (skodak)
9752   */
9753  class admin_setting_manageexternalservices extends admin_setting {
9754      /**
9755       * Calls parent::__construct with specific arguments
9756       */
9757      public function __construct() {
9758          $this->nosave = true;
9759          parent::__construct('webservicesui', get_string('externalservices', 'webservice'), '', '');
9760      }
9761  
9762      /**
9763       * Always returns true, does nothing
9764       *
9765       * @return true
9766       */
9767      public function get_setting() {
9768          return true;
9769      }
9770  
9771      /**
9772       * Always returns true, does nothing
9773       *
9774       * @return true
9775       */
9776      public function get_defaultsetting() {
9777          return true;
9778      }
9779  
9780      /**
9781       * Always returns '', does not write anything
9782       *
9783       * @return string Always returns ''
9784       */
9785      public function write_setting($data) {
9786      // do not write any setting
9787          return '';
9788      }
9789  
9790      /**
9791       * Checks if $query is one of the available external services
9792       *
9793       * @param string $query The string to search for
9794       * @return bool Returns true if found, false if not
9795       */
9796      public function is_related($query) {
9797          global $DB;
9798  
9799          if (parent::is_related($query)) {
9800              return true;
9801          }
9802  
9803          $services = $DB->get_records('external_services', array(), 'id, name');
9804          foreach ($services as $service) {
9805              if (strpos(core_text::strtolower($service->name), $query) !== false) {
9806                  return true;
9807              }
9808          }
9809          return false;
9810      }
9811  
9812      /**
9813       * Builds the XHTML to display the control
9814       *
9815       * @param string $data Unused
9816       * @param string $query
9817       * @return string
9818       */
9819      public function output_html($data, $query='') {
9820          global $CFG, $OUTPUT, $DB;
9821  
9822          // display strings
9823          $stradministration = get_string('administration');
9824          $stredit = get_string('edit');
9825          $strservice = get_string('externalservice', 'webservice');
9826          $strdelete = get_string('delete');
9827          $strplugin = get_string('plugin', 'admin');
9828          $stradd = get_string('add');
9829          $strfunctions = get_string('functions', 'webservice');
9830          $strusers = get_string('users');
9831          $strserviceusers = get_string('serviceusers', 'webservice');
9832  
9833          $esurl = "$CFG->wwwroot/$CFG->admin/webservice/service.php";
9834          $efurl = "$CFG->wwwroot/$CFG->admin/webservice/service_functions.php";
9835          $euurl = "$CFG->wwwroot/$CFG->admin/webservice/service_users.php";
9836  
9837          // built in services
9838           $services = $DB->get_records_select('external_services', 'component IS NOT NULL', null, 'name');
9839           $return = "";
9840           if (!empty($services)) {
9841              $return .= $OUTPUT->heading(get_string('servicesbuiltin', 'webservice'), 3, 'main');
9842  
9843  
9844  
9845              $table = new html_table();
9846              $table->head  = array($strservice, $strplugin, $strfunctions, $strusers, $stredit);
9847              $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9848              $table->id = 'builtinservices';
9849              $table->attributes['class'] = 'admintable externalservices generaltable';
9850              $table->data  = array();
9851  
9852              // iterate through auth plugins and add to the display table
9853              foreach ($services as $service) {
9854                  $name = $service->name;
9855  
9856                  // hide/show link
9857                  if ($service->enabled) {
9858                      $displayname = "<span>$name</span>";
9859                  } else {
9860                      $displayname = "<span class=\"dimmed_text\">$name</span>";
9861                  }
9862  
9863                  $plugin = $service->component;
9864  
9865                  $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9866  
9867                  if ($service->restrictedusers) {
9868                      $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9869                  } else {
9870                      $users = get_string('allusers', 'webservice');
9871                  }
9872  
9873                  $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9874  
9875                  // add a row to the table
9876                  $table->data[] = array($displayname, $plugin, $functions, $users, $edit);
9877              }
9878              $return .= html_writer::table($table);
9879          }
9880  
9881          // Custom services
9882          $return .= $OUTPUT->heading(get_string('servicescustom', 'webservice'), 3, 'main');
9883          $services = $DB->get_records_select('external_services', 'component IS NULL', null, 'name');
9884  
9885          $table = new html_table();
9886          $table->head  = array($strservice, $strdelete, $strfunctions, $strusers, $stredit);
9887          $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9888          $table->id = 'customservices';
9889          $table->attributes['class'] = 'admintable externalservices generaltable';
9890          $table->data  = array();
9891  
9892          // iterate through auth plugins and add to the display table
9893          foreach ($services as $service) {
9894              $name = $service->name;
9895  
9896              // hide/show link
9897              if ($service->enabled) {
9898                  $displayname = "<span>$name</span>";
9899              } else {
9900                  $displayname = "<span class=\"dimmed_text\">$name</span>";
9901              }
9902  
9903              // delete link
9904              $delete = "<a href=\"$esurl?action=delete&amp;sesskey=".sesskey()."&amp;id=$service->id\">$strdelete</a>";
9905  
9906              $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9907  
9908              if ($service->restrictedusers) {
9909                  $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9910              } else {
9911                  $users = get_string('allusers', 'webservice');
9912              }
9913  
9914              $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9915  
9916              // add a row to the table
9917              $table->data[] = array($displayname, $delete, $functions, $users, $edit);
9918          }
9919          // add new custom service option
9920          $return .= html_writer::table($table);
9921  
9922          $return .= '<br />';
9923          // add a token to the table
9924          $return .= "<a href=\"$esurl?id=0\">$stradd</a>";
9925  
9926          return highlight($query, $return);
9927      }
9928  }
9929  
9930  /**
9931   * Special class for overview of external services
9932   *
9933   * @author Jerome Mouneyrac
9934   */
9935  class admin_setting_webservicesoverview extends admin_setting {
9936  
9937      /**
9938       * Calls parent::__construct with specific arguments
9939       */
9940      public function __construct() {
9941          $this->nosave = true;
9942          parent::__construct('webservicesoverviewui',
9943                          get_string('webservicesoverview', 'webservice'), '', '');
9944      }
9945  
9946      /**
9947       * Always returns true, does nothing
9948       *
9949       * @return true
9950       */
9951      public function get_setting() {
9952          return true;
9953      }
9954  
9955      /**
9956       * Always returns true, does nothing
9957       *
9958       * @return true
9959       */
9960      public function get_defaultsetting() {
9961          return true;
9962      }
9963  
9964      /**
9965       * Always returns '', does not write anything
9966       *
9967       * @return string Always returns ''
9968       */
9969      public function write_setting($data) {
9970          // do not write any setting
9971          return '';
9972      }
9973  
9974      /**
9975       * Builds the XHTML to display the control
9976       *
9977       * @param string $data Unused
9978       * @param string $query
9979       * @return string
9980       */
9981      public function output_html($data, $query='') {
9982          global $CFG, $OUTPUT;
9983  
9984          $return = "";
9985          $brtag = html_writer::empty_tag('br');
9986  
9987          /// One system controlling Moodle with Token
9988          $return .= $OUTPUT->heading(get_string('onesystemcontrolling', 'webservice'), 3, 'main');
9989          $table = new html_table();
9990          $table->head = array(get_string('step', 'webservice'), get_string('status'),
9991              get_string('description'));
9992          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
9993          $table->id = 'onesystemcontrol';
9994          $table->attributes['class'] = 'admintable wsoverview generaltable';
9995          $table->data = array();
9996  
9997          $return .= $brtag . get_string('onesystemcontrollingdescription', 'webservice')
9998                  . $brtag . $brtag;
9999  
10000          /// 1. Enable Web Services
10001          $row = array();
10002          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10003          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10004                          array('href' => $url));
10005          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10006          if ($CFG->enablewebservices) {
10007              $status = get_string('yes');
10008          }
10009          $row[1] = $status;
10010          $row[2] = get_string('enablewsdescription', 'webservice');
10011          $table->data[] = $row;
10012  
10013          /// 2. Enable protocols
10014          $row = array();
10015          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10016          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10017                          array('href' => $url));
10018          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10019          //retrieve activated protocol
10020          $active_protocols = empty($CFG->webserviceprotocols) ?
10021                  array() : explode(',', $CFG->webserviceprotocols);
10022          if (!empty($active_protocols)) {
10023              $status = "";
10024              foreach ($active_protocols as $protocol) {
10025                  $status .= $protocol . $brtag;
10026              }
10027          }
10028          $row[1] = $status;
10029          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10030          $table->data[] = $row;
10031  
10032          /// 3. Create user account
10033          $row = array();
10034          $url = new moodle_url("/user/editadvanced.php?id=-1");
10035          $row[0] = "3. " . html_writer::tag('a', get_string('createuser', 'webservice'),
10036                          array('href' => $url));
10037          $row[1] = "";
10038          $row[2] = get_string('createuserdescription', 'webservice');
10039          $table->data[] = $row;
10040  
10041          /// 4. Add capability to users
10042          $row = array();
10043          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10044          $row[0] = "4. " . html_writer::tag('a', get_string('checkusercapability', 'webservice'),
10045                          array('href' => $url));
10046          $row[1] = "";
10047          $row[2] = get_string('checkusercapabilitydescription', 'webservice');
10048          $table->data[] = $row;
10049  
10050          /// 5. Select a web service
10051          $row = array();
10052          $url = new moodle_url("/admin/settings.php?section=externalservices");
10053          $row[0] = "5. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10054                          array('href' => $url));
10055          $row[1] = "";
10056          $row[2] = get_string('createservicedescription', 'webservice');
10057          $table->data[] = $row;
10058  
10059          /// 6. Add functions
10060          $row = array();
10061          $url = new moodle_url("/admin/settings.php?section=externalservices");
10062          $row[0] = "6. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10063                          array('href' => $url));
10064          $row[1] = "";
10065          $row[2] = get_string('addfunctionsdescription', 'webservice');
10066          $table->data[] = $row;
10067  
10068          /// 7. Add the specific user
10069          $row = array();
10070          $url = new moodle_url("/admin/settings.php?section=externalservices");
10071          $row[0] = "7. " . html_writer::tag('a', get_string('selectspecificuser', 'webservice'),
10072                          array('href' => $url));
10073          $row[1] = "";
10074          $row[2] = get_string('selectspecificuserdescription', 'webservice');
10075          $table->data[] = $row;
10076  
10077          /// 8. Create token for the specific user
10078          $row = array();
10079          $url = new moodle_url('/admin/webservice/tokens.php', ['action' => 'create']);
10080          $row[0] = "8. " . html_writer::tag('a', get_string('createtokenforuser', 'webservice'),
10081                          array('href' => $url));
10082          $row[1] = "";
10083          $row[2] = get_string('createtokenforuserdescription', 'webservice');
10084          $table->data[] = $row;
10085  
10086          /// 9. Enable the documentation
10087          $row = array();
10088          $url = new moodle_url("/admin/search.php?query=enablewsdocumentation");
10089          $row[0] = "9. " . html_writer::tag('a', get_string('enabledocumentation', 'webservice'),
10090                          array('href' => $url));
10091          $status = '<span class="warning">' . get_string('no') . '</span>';
10092          if ($CFG->enablewsdocumentation) {
10093              $status = get_string('yes');
10094          }
10095          $row[1] = $status;
10096          $row[2] = get_string('enabledocumentationdescription', 'webservice');
10097          $table->data[] = $row;
10098  
10099          /// 10. Test the service
10100          $row = array();
10101          $url = new moodle_url("/admin/webservice/testclient.php");
10102          $row[0] = "10. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10103                          array('href' => $url));
10104          $row[1] = "";
10105          $row[2] = get_string('testwithtestclientdescription', 'webservice');
10106          $table->data[] = $row;
10107  
10108          $return .= html_writer::table($table);
10109  
10110          /// Users as clients with token
10111          $return .= $brtag . $brtag . $brtag;
10112          $return .= $OUTPUT->heading(get_string('userasclients', 'webservice'), 3, 'main');
10113          $table = new html_table();
10114          $table->head = array(get_string('step', 'webservice'), get_string('status'),
10115              get_string('description'));
10116          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
10117          $table->id = 'userasclients';
10118          $table->attributes['class'] = 'admintable wsoverview generaltable';
10119          $table->data = array();
10120  
10121          $return .= $brtag . get_string('userasclientsdescription', 'webservice') .
10122                  $brtag . $brtag;
10123  
10124          /// 1. Enable Web Services
10125          $row = array();
10126          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10127          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10128                          array('href' => $url));
10129          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10130          if ($CFG->enablewebservices) {
10131              $status = get_string('yes');
10132          }
10133          $row[1] = $status;
10134          $row[2] = get_string('enablewsdescription', 'webservice');
10135          $table->data[] = $row;
10136  
10137          /// 2. Enable protocols
10138          $row = array();
10139          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10140          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10141                          array('href' => $url));
10142          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10143          //retrieve activated protocol
10144          $active_protocols = empty($CFG->webserviceprotocols) ?
10145                  array() : explode(',', $CFG->webserviceprotocols);
10146          if (!empty($active_protocols)) {
10147              $status = "";
10148              foreach ($active_protocols as $protocol) {
10149                  $status .= $protocol . $brtag;
10150              }
10151          }
10152          $row[1] = $status;
10153          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10154          $table->data[] = $row;
10155  
10156  
10157          /// 3. Select a web service
10158          $row = array();
10159          $url = new moodle_url("/admin/settings.php?section=externalservices");
10160          $row[0] = "3. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10161                          array('href' => $url));
10162          $row[1] = "";
10163          $row[2] = get_string('createserviceforusersdescription', 'webservice');
10164          $table->data[] = $row;
10165  
10166          /// 4. Add functions
10167          $row = array();
10168          $url = new moodle_url("/admin/settings.php?section=externalservices");
10169          $row[0] = "4. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10170                          array('href' => $url));
10171          $row[1] = "";
10172          $row[2] = get_string('addfunctionsdescription', 'webservice');
10173          $table->data[] = $row;
10174  
10175          /// 5. Add capability to users
10176          $row = array();
10177          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10178          $row[0] = "5. " . html_writer::tag('a', get_string('addcapabilitytousers', 'webservice'),
10179                          array('href' => $url));
10180          $row[1] = "";
10181          $row[2] = get_string('addcapabilitytousersdescription', 'webservice');
10182          $table->data[] = $row;
10183  
10184          /// 6. Test the service
10185          $row = array();
10186          $url = new moodle_url("/admin/webservice/testclient.php");
10187          $row[0] = "6. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10188                          array('href' => $url));
10189          $row[1] = "";
10190          $row[2] = get_string('testauserwithtestclientdescription', 'webservice');
10191          $table->data[] = $row;
10192  
10193          $return .= html_writer::table($table);
10194  
10195          return highlight($query, $return);
10196      }
10197  
10198  }
10199  
10200  
10201  /**
10202   * Special class for web service protocol administration.
10203   *
10204   * @author Petr Skoda (skodak)
10205   */
10206  class admin_setting_managewebserviceprotocols extends admin_setting {
10207  
10208      /**
10209       * Calls parent::__construct with specific arguments
10210       */
10211      public function __construct() {
10212          $this->nosave = true;
10213          parent::__construct('webservicesui', get_string('manageprotocols', 'webservice'), '', '');
10214      }
10215  
10216      /**
10217       * Always returns true, does nothing
10218       *
10219       * @return true
10220       */
10221      public function get_setting() {
10222          return true;
10223      }
10224  
10225      /**
10226       * Always returns true, does nothing
10227       *
10228       * @return true
10229       */
10230      public function get_defaultsetting() {
10231          return true;
10232      }
10233  
10234      /**
10235       * Always returns '', does not write anything
10236       *
10237       * @return string Always returns ''
10238       */
10239      public function write_setting($data) {
10240      // do not write any setting
10241          return '';
10242      }
10243  
10244      /**
10245       * Checks if $query is one of the available webservices
10246       *
10247       * @param string $query The string to search for
10248       * @return bool Returns true if found, false if not
10249       */
10250      public function is_related($query) {
10251          if (parent::is_related($query)) {
10252              return true;
10253          }
10254  
10255          $protocols = core_component::get_plugin_list('webservice');
10256          foreach ($protocols as $protocol=>$location) {
10257              if (strpos($protocol, $query) !== false) {
10258                  return true;
10259              }
10260              $protocolstr = get_string('pluginname', 'webservice_'.$protocol);
10261              if (strpos(core_text::strtolower($protocolstr), $query) !== false) {
10262                  return true;
10263              }
10264          }
10265          return false;
10266      }
10267  
10268      /**
10269       * Builds the XHTML to display the control
10270       *
10271       * @param string $data Unused
10272       * @param string $query
10273       * @return string
10274       */
10275      public function output_html($data, $query='') {
10276          global $CFG, $OUTPUT;
10277  
10278          // display strings
10279          $stradministration = get_string('administration');
10280          $strsettings = get_string('settings');
10281          $stredit = get_string('edit');
10282          $strprotocol = get_string('protocol', 'webservice');
10283          $strenable = get_string('enable');
10284          $strdisable = get_string('disable');
10285          $strversion = get_string('version');
10286  
10287          $protocols_available = core_component::get_plugin_list('webservice');
10288          $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
10289          ksort($protocols_available);
10290  
10291          foreach ($activeprotocols as $key => $protocol) {
10292              if (empty($protocols_available[$protocol])) {
10293                  unset($activeprotocols[$key]);
10294              }
10295          }
10296  
10297          $return = $OUTPUT->heading(get_string('actwebserviceshhdr', 'webservice'), 3, 'main');
10298          if (in_array('xmlrpc', $activeprotocols)) {
10299              $notify = new \core\output\notification(get_string('xmlrpcwebserviceenabled', 'admin'),
10300                  \core\output\notification::NOTIFY_WARNING);
10301              $return .= $OUTPUT->render($notify);
10302          }
10303          $return .= $OUTPUT->box_start('generalbox webservicesui');
10304  
10305          $table = new html_table();
10306          $table->head  = array($strprotocol, $strversion, $strenable, $strsettings);
10307          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
10308          $table->id = 'webserviceprotocols';
10309          $table->attributes['class'] = 'admintable generaltable';
10310          $table->data  = array();
10311  
10312          // iterate through auth plugins and add to the display table
10313          $url = "$CFG->wwwroot/$CFG->admin/webservice/protocols.php?sesskey=" . sesskey();
10314          foreach ($protocols_available as $protocol => $location) {
10315              $name = get_string('pluginname', 'webservice_'.$protocol);
10316  
10317              $plugin = new stdClass();
10318              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/version.php')) {
10319                  include($CFG->dirroot.'/webservice/'.$protocol.'/version.php');
10320              }
10321              $version = isset($plugin->version) ? $plugin->version : '';
10322  
10323              // hide/show link
10324              if (in_array($protocol, $activeprotocols)) {
10325                  $hideshow = "<a href=\"$url&amp;action=disable&amp;webservice=$protocol\">";
10326                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
10327                  $displayname = "<span>$name</span>";
10328              } else {
10329                  $hideshow = "<a href=\"$url&amp;action=enable&amp;webservice=$protocol\">";
10330                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
10331                  $displayname = "<span class=\"dimmed_text\">$name</span>";
10332              }
10333  
10334              // settings link
10335              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/settings.php')) {
10336                  $settings = "<a href=\"settings.php?section=webservicesetting$protocol\">$strsettings</a>";
10337              } else {
10338                  $settings = '';
10339              }
10340  
10341              // add a row to the table
10342              $table->data[] = array($displayname, $version, $hideshow, $settings);
10343          }
10344          $return .= html_writer::table($table);
10345          $return .= get_string('configwebserviceplugins', 'webservice');
10346          $return .= $OUTPUT->box_end();
10347  
10348          return highlight($query, $return);
10349      }
10350  }
10351  
10352  /**
10353   * Colour picker
10354   *
10355   * @copyright 2010 Sam Hemelryk
10356   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10357   */
10358  class admin_setting_configcolourpicker extends admin_setting {
10359  
10360      /**
10361       * Information for previewing the colour
10362       *
10363       * @var array|null
10364       */
10365      protected $previewconfig = null;
10366  
10367      /**
10368       * Use default when empty.
10369       */
10370      protected $usedefaultwhenempty = true;
10371  
10372      /**
10373       *
10374       * @param string $name
10375       * @param string $visiblename
10376       * @param string $description
10377       * @param string $defaultsetting
10378       * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor');
10379       */
10380      public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig = null,
10381              $usedefaultwhenempty = true) {
10382          $this->previewconfig = $previewconfig;
10383          $this->usedefaultwhenempty = $usedefaultwhenempty;
10384          parent::__construct($name, $visiblename, $description, $defaultsetting);
10385          $this->set_force_ltr(true);
10386      }
10387  
10388      /**
10389       * Return the setting
10390       *
10391       * @return mixed returns config if successful else null
10392       */
10393      public function get_setting() {
10394          return $this->config_read($this->name);
10395      }
10396  
10397      /**
10398       * Saves the setting
10399       *
10400       * @param string $data
10401       * @return bool
10402       */
10403      public function write_setting($data) {
10404          $data = $this->validate($data);
10405          if ($data === false) {
10406              return  get_string('validateerror', 'admin');
10407          }
10408          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
10409      }
10410  
10411      /**
10412       * Validates the colour that was entered by the user
10413       *
10414       * @param string $data
10415       * @return string|false
10416       */
10417      protected function validate($data) {
10418          /**
10419           * List of valid HTML colour names
10420           *
10421           * @var array
10422           */
10423           $colornames = array(
10424              'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure',
10425              'beige', 'bisque', 'black', 'blanchedalmond', 'blue',
10426              'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse',
10427              'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson',
10428              'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray',
10429              'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta',
10430              'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
10431              'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray',
10432              'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
10433              'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick',
10434              'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro',
10435              'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green',
10436              'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo',
10437              'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen',
10438              'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',
10439              'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen',
10440              'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue',
10441              'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow',
10442              'lime', 'limegreen', 'linen', 'magenta', 'maroon',
10443              'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',
10444              'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
10445              'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',
10446              'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',
10447              'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',
10448              'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',
10449              'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',
10450              'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown',
10451              'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',
10452              'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan',
10453              'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white',
10454              'whitesmoke', 'yellow', 'yellowgreen'
10455          );
10456  
10457          if (preg_match('/^#?([[:xdigit:]]{3}){1,2}$/', $data)) {
10458              if (strpos($data, '#')!==0) {
10459                  $data = '#'.$data;
10460              }
10461              return $data;
10462          } else if (in_array(strtolower($data), $colornames)) {
10463              return $data;
10464          } else if (preg_match('/rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)/i', $data)) {
10465              return $data;
10466          } else if (preg_match('/rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)/i', $data)) {
10467              return $data;
10468          } else if (preg_match('/hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)/i', $data)) {
10469              return $data;
10470          } else if (preg_match('/hsla\(\d{0,3}\, ?\d{0,3}%,\d{0,3}%\, ?\d(\.\d)?\)/i', $data)) {
10471              return $data;
10472          } else if (($data == 'transparent') || ($data == 'currentColor') || ($data == 'inherit')) {
10473              return $data;
10474          } else if (empty($data)) {
10475              if ($this->usedefaultwhenempty){
10476                  return $this->defaultsetting;
10477              } else {
10478                  return '';
10479              }
10480          } else {
10481              return false;
10482          }
10483      }
10484  
10485      /**
10486       * Generates the HTML for the setting
10487       *
10488       * @global moodle_page $PAGE
10489       * @global core_renderer $OUTPUT
10490       * @param string $data
10491       * @param string $query
10492       */
10493      public function output_html($data, $query = '') {
10494          global $PAGE, $OUTPUT;
10495  
10496          $icon = new pix_icon('i/loading', get_string('loading', 'admin'), 'moodle', ['class' => 'loadingicon']);
10497          $context = (object) [
10498              'id' => $this->get_id(),
10499              'name' => $this->get_full_name(),
10500              'value' => $data,
10501              'icon' => $icon->export_for_template($OUTPUT),
10502              'haspreviewconfig' => !empty($this->previewconfig),
10503              'forceltr' => $this->get_force_ltr(),
10504              'readonly' => $this->is_readonly(),
10505          ];
10506  
10507          $element = $OUTPUT->render_from_template('core_admin/setting_configcolourpicker', $context);
10508          $PAGE->requires->js_init_call('M.util.init_colour_picker', array($this->get_id(), $this->previewconfig));
10509  
10510          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '',
10511              $this->get_defaultsetting(), $query);
10512      }
10513  
10514  }
10515  
10516  
10517  /**
10518   * Class used for uploading of one file into file storage,
10519   * the file name is stored in config table.
10520   *
10521   * Please note you need to implement your own '_pluginfile' callback function,
10522   * this setting only stores the file, it does not deal with file serving.
10523   *
10524   * @copyright 2013 Petr Skoda {@link http://skodak.org}
10525   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10526   */
10527  class admin_setting_configstoredfile extends admin_setting {
10528      /** @var array file area options - should be one file only */
10529      protected $options;
10530      /** @var string name of the file area */
10531      protected $filearea;
10532      /** @var int intemid */
10533      protected $itemid;
10534      /** @var string used for detection of changes */
10535      protected $oldhashes;
10536  
10537      /**
10538       * Create new stored file setting.
10539       *
10540       * @param string $name low level setting name
10541       * @param string $visiblename human readable setting name
10542       * @param string $description description of setting
10543       * @param mixed $filearea file area for file storage
10544       * @param int $itemid itemid for file storage
10545       * @param array $options file area options
10546       */
10547      public function __construct($name, $visiblename, $description, $filearea, $itemid = 0, array $options = null) {
10548          parent::__construct($name, $visiblename, $description, '');
10549          $this->filearea = $filearea;
10550          $this->itemid   = $itemid;
10551          $this->options  = (array)$options;
10552          $this->customcontrol = true;
10553      }
10554  
10555      /**
10556       * Applies defaults and returns all options.
10557       * @return array
10558       */
10559      protected function get_options() {
10560          global $CFG;
10561  
10562          require_once("$CFG->libdir/filelib.php");
10563          require_once("$CFG->dirroot/repository/lib.php");
10564          $defaults = array(
10565              'mainfile' => '', 'subdirs' => 0, 'maxbytes' => -1, 'maxfiles' => 1,
10566              'accepted_types' => '*', 'return_types' => FILE_INTERNAL, 'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED,
10567              'context' => context_system::instance());
10568          foreach($this->options as $k => $v) {
10569              $defaults[$k] = $v;
10570          }
10571  
10572          return $defaults;
10573      }
10574  
10575      public function get_setting() {
10576          return $this->config_read($this->name);
10577      }
10578  
10579      public function write_setting($data) {
10580          global $USER;
10581  
10582          // Let's not deal with validation here, this is for admins only.
10583          $current = $this->get_setting();
10584          if (empty($data) && ($current === null || $current === '')) {
10585              // This will be the case when applying default settings (installation).
10586              return ($this->config_write($this->name, '') ? '' : get_string('errorsetting', 'admin'));
10587          } else if (!is_number($data)) {
10588              // Draft item id is expected here!
10589              return get_string('errorsetting', 'admin');
10590          }
10591  
10592          $options = $this->get_options();
10593          $fs = get_file_storage();
10594          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10595  
10596          $this->oldhashes = null;
10597          if ($current) {
10598              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10599              if ($file = $fs->get_file_by_hash($hash)) {
10600                  $this->oldhashes = $file->get_contenthash().$file->get_pathnamehash();
10601              }
10602              unset($file);
10603          }
10604  
10605          if ($fs->file_exists($options['context']->id, $component, $this->filearea, $this->itemid, '/', '.')) {
10606              // Make sure the settings form was not open for more than 4 days and draft areas deleted in the meantime.
10607              // But we can safely ignore that if the destination area is empty, so that the user is not prompt
10608              // with an error because the draft area does not exist, as he did not use it.
10609              $usercontext = context_user::instance($USER->id);
10610              if (!$fs->file_exists($usercontext->id, 'user', 'draft', $data, '/', '.') && $current !== '') {
10611                  return get_string('errorsetting', 'admin');
10612              }
10613          }
10614  
10615          file_save_draft_area_files($data, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10616          $files = $fs->get_area_files($options['context']->id, $component, $this->filearea, $this->itemid, 'sortorder,filepath,filename', false);
10617  
10618          $filepath = '';
10619          if ($files) {
10620              /** @var stored_file $file */
10621              $file = reset($files);
10622              $filepath = $file->get_filepath().$file->get_filename();
10623          }
10624  
10625          return ($this->config_write($this->name, $filepath) ? '' : get_string('errorsetting', 'admin'));
10626      }
10627  
10628      public function post_write_settings($original) {
10629          $options = $this->get_options();
10630          $fs = get_file_storage();
10631          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10632  
10633          $current = $this->get_setting();
10634          $newhashes = null;
10635          if ($current) {
10636              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10637              if ($file = $fs->get_file_by_hash($hash)) {
10638                  $newhashes = $file->get_contenthash().$file->get_pathnamehash();
10639              }
10640              unset($file);
10641          }
10642  
10643          if ($this->oldhashes === $newhashes) {
10644              $this->oldhashes = null;
10645              return false;
10646          }
10647          $this->oldhashes = null;
10648  
10649          $callbackfunction = $this->updatedcallback;
10650          if (!empty($callbackfunction) and function_exists($callbackfunction)) {
10651              $callbackfunction($this->get_full_name());
10652          }
10653          return true;
10654      }
10655  
10656      public function output_html($data, $query = '') {
10657          global $CFG;
10658  
10659          $options = $this->get_options();
10660          $id = $this->get_id();
10661          $elname = $this->get_full_name();
10662          $draftitemid = file_get_submitted_draft_itemid($elname);
10663          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10664          file_prepare_draft_area($draftitemid, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10665  
10666          // Filemanager form element implementation is far from optimal, we need to rework this if we ever fix it...
10667          require_once("$CFG->dirroot/lib/form/filemanager.php");
10668  
10669          $fmoptions = new stdClass();
10670          $fmoptions->mainfile       = $options['mainfile'];
10671          $fmoptions->maxbytes       = $options['maxbytes'];
10672          $fmoptions->maxfiles       = $options['maxfiles'];
10673          $fmoptions->subdirs        = $options['subdirs'];
10674          $fmoptions->accepted_types = $options['accepted_types'];
10675          $fmoptions->return_types   = $options['return_types'];
10676          $fmoptions->context        = $options['context'];
10677          $fmoptions->areamaxbytes   = $options['areamaxbytes'];
10678  
10679          $fm = new MoodleQuickForm_filemanager($elname, $this->visiblename, ['id' => $id], $fmoptions);
10680          $fm->setValue($draftitemid);
10681  
10682          return format_admin_setting($this, $this->visiblename,
10683              '<div class="form-filemanager" data-fieldtype="filemanager">' . $fm->toHtml() . '</div>',
10684              $this->description, true, '', '', $query);
10685      }
10686  }
10687  
10688  
10689  /**
10690   * Administration interface for user specified regular expressions for device detection.
10691   *
10692   * @deprecated Moodle 4.3 MDL-78468 - No longer used since the devicedetectregex was removed.
10693   * @todo Final deprecation on Moodle 4.7 MDL-79052
10694   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10695   */
10696  class admin_setting_devicedetectregex extends admin_setting {
10697  
10698      /**
10699       * Calls parent::__construct with specific args
10700       *
10701       * @deprecated Moodle 4.3 MDL-78468 - No longer used since the devicedetectregex was removed.
10702       * @todo Final deprecation on Moodle 4.7 MDL-79052
10703       * @param string $name
10704       * @param string $visiblename
10705       * @param string $description
10706       * @param mixed $defaultsetting
10707       */
10708      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
10709          debugging(
10710              __FUNCTION__ . '() is deprecated.' .
10711                  'All functions associated with devicedetectregex theme setting are being removed.',
10712              DEBUG_DEVELOPER
10713          );
10714          global $CFG;
10715          parent::__construct($name, $visiblename, $description, $defaultsetting);
10716      }
10717  
10718      /**
10719       * Return the current setting(s)
10720       *
10721       * @deprecated Moodle 4.3 MDL-78468 - No longer used since the devicedetectregex was removed.
10722       * @todo Final deprecation on Moodle 4.7 MDL-79052
10723       * @return array Current settings array
10724       */
10725      public function get_setting() {
10726          debugging(
10727              __FUNCTION__ . '() is deprecated.' .
10728                  'All functions associated with devicedetectregex theme setting are being removed.',
10729              DEBUG_DEVELOPER
10730          );
10731          global $CFG;
10732  
10733          $config = $this->config_read($this->name);
10734          if (is_null($config)) {
10735              return null;
10736          }
10737  
10738          return $this->prepare_form_data($config);
10739      }
10740  
10741      /**
10742       * Save selected settings
10743       *
10744       * @deprecated Moodle 4.3 MDL-78468 - No longer used since the devicedetectregex was removed.
10745       * @todo Final deprecation on Moodle 4.7 MDL-79052
10746       * @param array $data Array of settings to save
10747       * @return bool
10748       */
10749      public function write_setting($data) {
10750          debugging(
10751              __FUNCTION__ . '() is deprecated.' .
10752                  'All functions associated with devicedetectregex theme setting are being removed.',
10753              DEBUG_DEVELOPER
10754          );
10755          if (empty($data)) {
10756              $data = array();
10757          }
10758  
10759          if ($this->config_write($this->name, $this->process_form_data($data))) {
10760              return ''; // success
10761          } else {
10762              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
10763          }
10764      }
10765  
10766      /**
10767       * Return XHTML field(s) for regexes
10768       *
10769       * @deprecated Moodle 4.3 MDL-78468 - No longer used since the devicedetectregex was removed.
10770       * @todo Final deprecation on Moodle 4.7 MDL-79052
10771       * @param array $data Array of options to set in HTML
10772       * @return string XHTML string for the fields and wrapping div(s)
10773       */
10774      public function output_html($data, $query='') {
10775          debugging(
10776              __FUNCTION__ . '() is deprecated.' .
10777                  'All functions associated with devicedetectregex theme setting are being removed.',
10778              DEBUG_DEVELOPER
10779          );
10780          global $OUTPUT;
10781  
10782          $context = (object) [
10783              'expressions' => [],
10784              'name' => $this->get_full_name()
10785          ];
10786  
10787          if (empty($data)) {
10788              $looplimit = 1;
10789          } else {
10790              $looplimit = (count($data)/2)+1;
10791          }
10792  
10793          for ($i=0; $i<$looplimit; $i++) {
10794  
10795              $expressionname = 'expression'.$i;
10796  
10797              if (!empty($data[$expressionname])){
10798                  $expression = $data[$expressionname];
10799              } else {
10800                  $expression = '';
10801              }
10802  
10803              $valuename = 'value'.$i;
10804  
10805              if (!empty($data[$valuename])){
10806                  $value = $data[$valuename];
10807              } else {
10808                  $value= '';
10809              }
10810  
10811              $context->expressions[] = [
10812                  'index' => $i,
10813                  'expression' => $expression,
10814                  'value' => $value
10815              ];
10816          }
10817  
10818          $element = $OUTPUT->render_from_template('core_admin/setting_devicedetectregex', $context);
10819  
10820          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
10821      }
10822  
10823      /**
10824       * Converts the string of regexes
10825       *
10826       * @see self::process_form_data()
10827       * @param $regexes string of regexes
10828       * @return array of form fields and their values
10829       */
10830      protected function prepare_form_data($regexes) {
10831  
10832          $regexes = json_decode($regexes);
10833  
10834          $form = array();
10835  
10836          $i = 0;
10837  
10838          foreach ($regexes as $value => $regex) {
10839              $expressionname  = 'expression'.$i;
10840              $valuename = 'value'.$i;
10841  
10842              $form[$expressionname] = $regex;
10843              $form[$valuename] = $value;
10844              $i++;
10845          }
10846  
10847          return $form;
10848      }
10849  
10850      /**
10851       * Converts the data from admin settings form into a string of regexes
10852       *
10853       * @see self::prepare_form_data()
10854       * @param array $data array of admin form fields and values
10855       * @return false|string of regexes
10856       */
10857      protected function process_form_data(array $form) {
10858  
10859          $count = count($form); // number of form field values
10860  
10861          if ($count % 2) {
10862              // we must get five fields per expression
10863              return false;
10864          }
10865  
10866          $regexes = array();
10867          for ($i = 0; $i < $count / 2; $i++) {
10868              $expressionname  = "expression".$i;
10869              $valuename       = "value".$i;
10870  
10871              $expression = trim($form['expression'.$i]);
10872              $value      = trim($form['value'.$i]);
10873  
10874              if (empty($expression)){
10875                  continue;
10876              }
10877  
10878              $regexes[$value] = $expression;
10879          }
10880  
10881          $regexes = json_encode($regexes);
10882  
10883          return $regexes;
10884      }
10885  
10886  }
10887  
10888  /**
10889   * Multiselect for current modules
10890   *
10891   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10892   */
10893  class admin_setting_configmultiselect_modules extends admin_setting_configmultiselect {
10894      private $excludesystem;
10895  
10896      /**
10897       * Calls parent::__construct - note array $choices is not required
10898       *
10899       * @param string $name setting name
10900       * @param string $visiblename localised setting name
10901       * @param string $description setting description
10902       * @param array $defaultsetting a plain array of default module ids
10903       * @param bool $excludesystem If true, excludes modules with 'system' archetype
10904       */
10905      public function __construct($name, $visiblename, $description, $defaultsetting = array(),
10906              $excludesystem = true) {
10907          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
10908          $this->excludesystem = $excludesystem;
10909      }
10910  
10911      /**
10912       * Loads an array of current module choices
10913       *
10914       * @return bool always return true
10915       */
10916      public function load_choices() {
10917          if (is_array($this->choices)) {
10918              return true;
10919          }
10920          $this->choices = array();
10921  
10922          global $CFG, $DB;
10923          $records = $DB->get_records('modules', array('visible'=>1), 'name');
10924          foreach ($records as $record) {
10925              // Exclude modules if the code doesn't exist
10926              if (file_exists("$CFG->dirroot/mod/$record->name/lib.php")) {
10927                  // Also exclude system modules (if specified)
10928                  if (!($this->excludesystem &&
10929                          plugin_supports('mod', $record->name, FEATURE_MOD_ARCHETYPE) ===
10930                          MOD_ARCHETYPE_SYSTEM)) {
10931                      $this->choices[$record->id] = $record->name;
10932                  }
10933              }
10934          }
10935          return true;
10936      }
10937  }
10938  
10939  /**
10940   * Admin setting to show if a php extension is enabled or not.
10941   *
10942   * @copyright 2013 Damyon Wiese
10943   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10944   */
10945  class admin_setting_php_extension_enabled extends admin_setting {
10946  
10947      /** @var string The name of the extension to check for */
10948      private $extension;
10949  
10950      /**
10951       * Calls parent::__construct with specific arguments
10952       */
10953      public function __construct($name, $visiblename, $description, $extension) {
10954          $this->extension = $extension;
10955          $this->nosave = true;
10956          parent::__construct($name, $visiblename, $description, '');
10957      }
10958  
10959      /**
10960       * Always returns true, does nothing
10961       *
10962       * @return true
10963       */
10964      public function get_setting() {
10965          return true;
10966      }
10967  
10968      /**
10969       * Always returns true, does nothing
10970       *
10971       * @return true
10972       */
10973      public function get_defaultsetting() {
10974          return true;
10975      }
10976  
10977      /**
10978       * Always returns '', does not write anything
10979       *
10980       * @return string Always returns ''
10981       */
10982      public function write_setting($data) {
10983          // Do not write any setting.
10984          return '';
10985      }
10986  
10987      /**
10988       * Outputs the html for this setting.
10989       * @return string Returns an XHTML string
10990       */
10991      public function output_html($data, $query='') {
10992          global $OUTPUT;
10993  
10994          $o = '';
10995          if (!extension_loaded($this->extension)) {
10996              $warning = $OUTPUT->pix_icon('i/warning', '', '', array('role' => 'presentation')) . ' ' . $this->description;
10997  
10998              $o .= format_admin_setting($this, $this->visiblename, $warning);
10999          }
11000          return $o;
11001      }
11002  }
11003  
11004  /**
11005   * Server timezone setting.
11006   *
11007   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11008   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11009   * @author    Petr Skoda <petr.skoda@totaralms.com>
11010   */
11011  class admin_setting_servertimezone extends admin_setting_configselect {
11012      /**
11013       * Constructor.
11014       */
11015      public function __construct() {
11016          $default = core_date::get_default_php_timezone();
11017          if ($default === 'UTC') {
11018              // Nobody really wants UTC, so instead default selection to the country that is confused by the UTC the most.
11019              $default = 'Europe/London';
11020          }
11021  
11022          parent::__construct('timezone',
11023              new lang_string('timezone', 'core_admin'),
11024              new lang_string('configtimezone', 'core_admin'), $default, null);
11025      }
11026  
11027      /**
11028       * Lazy load timezone options.
11029       * @return bool true if loaded, false if error
11030       */
11031      public function load_choices() {
11032          global $CFG;
11033          if (is_array($this->choices)) {
11034              return true;
11035          }
11036  
11037          $current = isset($CFG->timezone) ? $CFG->timezone : null;
11038          $this->choices = core_date::get_list_of_timezones($current, false);
11039          if ($current == 99) {
11040              // Do not show 99 unless it is current value, we want to get rid of it over time.
11041              $this->choices['99'] = new lang_string('timezonephpdefault', 'core_admin',
11042                  core_date::get_default_php_timezone());
11043          }
11044  
11045          return true;
11046      }
11047  }
11048  
11049  /**
11050   * Forced user timezone setting.
11051   *
11052   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11053   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11054   * @author    Petr Skoda <petr.skoda@totaralms.com>
11055   */
11056  class admin_setting_forcetimezone extends admin_setting_configselect {
11057      /**
11058       * Constructor.
11059       */
11060      public function __construct() {
11061          parent::__construct('forcetimezone',
11062              new lang_string('forcetimezone', 'core_admin'),
11063              new lang_string('helpforcetimezone', 'core_admin'), '99', null);
11064      }
11065  
11066      /**
11067       * Lazy load timezone options.
11068       * @return bool true if loaded, false if error
11069       */
11070      public function load_choices() {
11071          global $CFG;
11072          if (is_array($this->choices)) {
11073              return true;
11074          }
11075  
11076          $current = isset($CFG->forcetimezone) ? $CFG->forcetimezone : null;
11077          $this->choices = core_date::get_list_of_timezones($current, true);
11078          $this->choices['99'] = new lang_string('timezonenotforced', 'core_admin');
11079  
11080          return true;
11081      }
11082  }
11083  
11084  
11085  /**
11086   * Search setup steps info.
11087   *
11088   * @package core
11089   * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
11090   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11091   */
11092  class admin_setting_searchsetupinfo extends admin_setting {
11093  
11094      /**
11095       * Calls parent::__construct with specific arguments
11096       */
11097      public function __construct() {
11098          $this->nosave = true;
11099          parent::__construct('searchsetupinfo', '', '', '');
11100      }
11101  
11102      /**
11103       * Always returns true, does nothing
11104       *
11105       * @return true
11106       */
11107      public function get_setting() {
11108          return true;
11109      }
11110  
11111      /**
11112       * Always returns true, does nothing
11113       *
11114       * @return true
11115       */
11116      public function get_defaultsetting() {
11117          return true;
11118      }
11119  
11120      /**
11121       * Always returns '', does not write anything
11122       *
11123       * @param array $data
11124       * @return string Always returns ''
11125       */
11126      public function write_setting($data) {
11127          // Do not write any setting.
11128          return '';
11129      }
11130  
11131      /**
11132       * Builds the HTML to display the control
11133       *
11134       * @param string $data Unused
11135       * @param string $query
11136       * @return string
11137       */
11138      public function output_html($data, $query='') {
11139          global $CFG, $OUTPUT, $ADMIN;
11140  
11141          $return = '';
11142          $brtag = html_writer::empty_tag('br');
11143  
11144          $searchareas = \core_search\manager::get_search_areas_list();
11145          $anyenabled = !empty(\core_search\manager::get_search_areas_list(true));
11146          $anyindexed = false;
11147          foreach ($searchareas as $areaid => $searcharea) {
11148              list($componentname, $varname) = $searcharea->get_config_var_name();
11149              if (get_config($componentname, $varname . '_indexingstart')) {
11150                  $anyindexed = true;
11151                  break;
11152              }
11153          }
11154  
11155          $return .= $OUTPUT->heading(get_string('searchsetupinfo', 'admin'), 3, 'main');
11156  
11157          $table = new html_table();
11158          $table->head = array(get_string('step', 'search'), get_string('status'));
11159          $table->colclasses = array('leftalign step', 'leftalign status');
11160          $table->id = 'searchsetup';
11161          $table->attributes['class'] = 'admintable generaltable';
11162          $table->data = array();
11163  
11164          $return .= $brtag . get_string('searchsetupdescription', 'search') . $brtag . $brtag;
11165  
11166          // Select a search engine.
11167          $row = array();
11168          $url = new moodle_url('/admin/settings.php?section=manageglobalsearch#admin-searchengine');
11169          $row[0] = '1. ' . html_writer::tag('a', get_string('selectsearchengine', 'admin'),
11170                          array('href' => $url));
11171  
11172          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11173          if (!empty($CFG->searchengine)) {
11174              $status = html_writer::tag('span', get_string('pluginname', 'search_' . $CFG->searchengine),
11175                  array('class' => 'badge badge-success'));
11176  
11177          }
11178          $row[1] = $status;
11179          $table->data[] = $row;
11180  
11181          // Available areas.
11182          $row = array();
11183          $url = new moodle_url('/admin/searchareas.php');
11184          $row[0] = '2. ' . html_writer::tag('a', get_string('enablesearchareas', 'admin'),
11185                          array('href' => $url));
11186  
11187          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11188          if ($anyenabled) {
11189              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11190  
11191          }
11192          $row[1] = $status;
11193          $table->data[] = $row;
11194  
11195          // Setup search engine.
11196          $row = array();
11197          if (empty($CFG->searchengine)) {
11198              $row[0] = '3. ' . get_string('setupsearchengine', 'admin');
11199              $row[1] = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11200          } else {
11201              if ($ADMIN->locate('search' . $CFG->searchengine)) {
11202                  $url = new moodle_url('/admin/settings.php?section=search' . $CFG->searchengine);
11203                  $row[0] = '3. ' . html_writer::link($url, get_string('setupsearchengine', 'core_admin'));
11204              } else {
11205                  $row[0] = '3. ' . get_string('setupsearchengine', 'core_admin');
11206              }
11207  
11208              // Check the engine status.
11209              $searchengine = \core_search\manager::search_engine_instance();
11210              try {
11211                  $serverstatus = $searchengine->is_server_ready();
11212              } catch (\moodle_exception $e) {
11213                  $serverstatus = $e->getMessage();
11214              }
11215              if ($serverstatus === true) {
11216                  $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11217              } else {
11218                  $status = html_writer::tag('span', $serverstatus, array('class' => 'badge badge-danger'));
11219              }
11220              $row[1] = $status;
11221          }
11222          $table->data[] = $row;
11223  
11224          // Indexed data.
11225          $row = array();
11226          $url = new moodle_url('/admin/searchareas.php');
11227          $row[0] = '4. ' . html_writer::tag('a', get_string('indexdata', 'admin'), array('href' => $url));
11228          if ($anyindexed) {
11229              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11230          } else {
11231              $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11232          }
11233          $row[1] = $status;
11234          $table->data[] = $row;
11235  
11236          // Enable global search.
11237          $row = array();
11238          $url = new moodle_url("/admin/search.php?query=enableglobalsearch");
11239          $row[0] = '5. ' . html_writer::tag('a', get_string('enableglobalsearch', 'admin'),
11240                          array('href' => $url));
11241          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11242          if (\core_search\manager::is_global_search_enabled()) {
11243              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11244          }
11245          $row[1] = $status;
11246          $table->data[] = $row;
11247  
11248          // Replace front page search.
11249          $row = array();
11250          $url = new moodle_url("/admin/search.php?query=searchincludeallcourses");
11251          $row[0] = '6. ' . html_writer::tag('a', get_string('replacefrontsearch', 'admin'),
11252                                             array('href' => $url));
11253          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11254          if (\core_search\manager::can_replace_course_search()) {
11255              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11256          }
11257          $row[1] = $status;
11258          $table->data[] = $row;
11259  
11260          $return .= html_writer::table($table);
11261  
11262          return highlight($query, $return);
11263      }
11264  
11265  }
11266  
11267  /**
11268   * Used to validate the contents of SCSS code and ensuring they are parsable.
11269   *
11270   * It does not attempt to detect undefined SCSS variables because it is designed
11271   * to be used without knowledge of other config/scss included.
11272   *
11273   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11274   * @copyright 2016 Dan Poltawski <dan@moodle.com>
11275   */
11276  class admin_setting_scsscode extends admin_setting_configtextarea {
11277  
11278      /**
11279       * Validate the contents of the SCSS to ensure its parsable. Does not
11280       * attempt to detect undefined scss variables.
11281       *
11282       * @param string $data The scss code from text field.
11283       * @return mixed bool true for success or string:error on failure.
11284       */
11285      public function validate($data) {
11286          if (empty($data)) {
11287              return true;
11288          }
11289  
11290          $scss = new core_scss();
11291          try {
11292              $scss->compile($data);
11293          } catch (ScssPhp\ScssPhp\Exception\ParserException $e) {
11294              return get_string('scssinvalid', 'admin', $e->getMessage());
11295          } catch (ScssPhp\ScssPhp\Exception\CompilerException $e) {
11296              // Silently ignore this - it could be a scss variable defined from somewhere
11297              // else which we are not examining here.
11298              return true;
11299          }
11300  
11301          return true;
11302      }
11303  }
11304  
11305  
11306  /**
11307   * Administration setting to define a list of file types.
11308   *
11309   * @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
11310   * @copyright 2017 David Mudrák <david@moodle.com>
11311   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11312   */
11313  class admin_setting_filetypes extends admin_setting_configtext {
11314  
11315      /** @var array Allow selection from these file types only. */
11316      protected $onlytypes = [];
11317  
11318      /** @var bool Allow selection of 'All file types' (will be stored as '*'). */
11319      protected $allowall = true;
11320  
11321      /** @var core_form\filetypes_util instance to use as a helper. */
11322      protected $util = null;
11323  
11324      /**
11325       * Constructor.
11326       *
11327       * @param string $name Unique ascii name like 'mycoresetting' or 'myplugin/mysetting'
11328       * @param string $visiblename Localised label of the setting
11329       * @param string $description Localised description of the setting
11330       * @param string $defaultsetting Default setting value.
11331       * @param array $options Setting widget options, an array with optional keys:
11332       *   'onlytypes' => array Allow selection from these file types only; for example ['onlytypes' => ['web_image']].
11333       *   'allowall' => bool Allow to select 'All file types', defaults to true. Does not apply if onlytypes are set.
11334       */
11335      public function __construct($name, $visiblename, $description, $defaultsetting = '', array $options = []) {
11336  
11337          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW);
11338  
11339          if (array_key_exists('onlytypes', $options) && is_array($options['onlytypes'])) {
11340              $this->onlytypes = $options['onlytypes'];
11341          }
11342  
11343          if (!$this->onlytypes && array_key_exists('allowall', $options)) {
11344              $this->allowall = (bool)$options['allowall'];
11345          }
11346  
11347          $this->util = new \core_form\filetypes_util();
11348      }
11349  
11350      /**
11351       * Normalize the user's input and write it to the database as comma separated list.
11352       *
11353       * Comma separated list as a text representation of the array was chosen to
11354       * make this compatible with how the $CFG->courseoverviewfilesext values are stored.
11355       *
11356       * @param string $data Value submitted by the admin.
11357       * @return string Epty string if all good, error message otherwise.
11358       */
11359      public function write_setting($data) {
11360          return parent::write_setting(implode(',', $this->util->normalize_file_types($data)));
11361      }
11362  
11363      /**
11364       * Validate data before storage
11365       *
11366       * @param string $data The setting values provided by the admin
11367       * @return bool|string True if ok, the string if error found
11368       */
11369      public function validate($data) {
11370          $parentcheck = parent::validate($data);
11371          if ($parentcheck !== true) {
11372              return $parentcheck;
11373          }
11374  
11375          // Check for unknown file types.
11376          if ($unknown = $this->util->get_unknown_file_types($data)) {
11377              return get_string('filetypesunknown', 'core_form', implode(', ', $unknown));
11378          }
11379  
11380          // Check for disallowed file types.
11381          if ($notlisted = $this->util->get_not_listed($data, $this->onlytypes)) {
11382              return get_string('filetypesnotallowed', 'core_form', implode(', ', $notlisted));
11383          }
11384  
11385          return true;
11386      }
11387  
11388      /**
11389       * Return an HTML string for the setting element.
11390       *
11391       * @param string $data The current setting value
11392       * @param string $query Admin search query to be highlighted
11393       * @return string HTML to be displayed
11394       */
11395      public function output_html($data, $query='') {
11396          global $OUTPUT, $PAGE;
11397  
11398          $default = $this->get_defaultsetting();
11399          $context = (object) [
11400              'id' => $this->get_id(),
11401              'name' => $this->get_full_name(),
11402              'value' => $data,
11403              'descriptions' => $this->util->describe_file_types($data),
11404          ];
11405          $element = $OUTPUT->render_from_template('core_admin/setting_filetypes', $context);
11406  
11407          $PAGE->requires->js_call_amd('core_form/filetypes', 'init', [
11408              $this->get_id(),
11409              $this->visiblename->out(),
11410              $this->onlytypes,
11411              $this->allowall,
11412          ]);
11413  
11414          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
11415      }
11416  
11417      /**
11418       * Should the values be always displayed in LTR mode?
11419       *
11420       * We always return true here because these values are not RTL compatible.
11421       *
11422       * @return bool True because these values are not RTL compatible.
11423       */
11424      public function get_force_ltr() {
11425          return true;
11426      }
11427  }
11428  
11429  /**
11430   * Used to validate the content and format of the age of digital consent map and ensuring it is parsable.
11431   *
11432   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11433   * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
11434   */
11435  class admin_setting_agedigitalconsentmap extends admin_setting_configtextarea {
11436  
11437      /**
11438       * Constructor.
11439       *
11440       * @param string $name
11441       * @param string $visiblename
11442       * @param string $description
11443       * @param mixed $defaultsetting string or array
11444       * @param mixed $paramtype
11445       * @param string $cols
11446       * @param string $rows
11447       */
11448      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype = PARAM_RAW,
11449                                  $cols = '60', $rows = '8') {
11450          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
11451          // Pre-set force LTR to false.
11452          $this->set_force_ltr(false);
11453      }
11454  
11455      /**
11456       * Validate the content and format of the age of digital consent map to ensure it is parsable.
11457       *
11458       * @param string $data The age of digital consent map from text field.
11459       * @return mixed bool true for success or string:error on failure.
11460       */
11461      public function validate($data) {
11462          if (empty($data)) {
11463              return true;
11464          }
11465  
11466          try {
11467              \core_auth\digital_consent::parse_age_digital_consent_map($data);
11468          } catch (\moodle_exception $e) {
11469              return get_string('invalidagedigitalconsent', 'admin', $e->getMessage());
11470          }
11471  
11472          return true;
11473      }
11474  }
11475  
11476  /**
11477   * Selection of plugins that can work as site policy handlers
11478   *
11479   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11480   * @copyright 2018 Marina Glancy
11481   */
11482  class admin_settings_sitepolicy_handler_select extends admin_setting_configselect {
11483  
11484      /**
11485       * Constructor
11486       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11487       *        for ones in config_plugins.
11488       * @param string $visiblename localised
11489       * @param string $description long localised info
11490       * @param string $defaultsetting
11491       */
11492      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11493          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11494      }
11495  
11496      /**
11497       * Lazy-load the available choices for the select box
11498       */
11499      public function load_choices() {
11500          if (during_initial_install()) {
11501              return false;
11502          }
11503          if (is_array($this->choices)) {
11504              return true;
11505          }
11506  
11507          $this->choices = ['' => new lang_string('sitepolicyhandlercore', 'core_admin')];
11508          $manager = new \core_privacy\local\sitepolicy\manager();
11509          $plugins = $manager->get_all_handlers();
11510          foreach ($plugins as $pname => $unused) {
11511              $this->choices[$pname] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11512                  ['name' => new lang_string('pluginname', $pname), 'component' => $pname]);
11513          }
11514  
11515          return true;
11516      }
11517  }
11518  
11519  /**
11520   * Used to validate theme presets code and ensuring they compile well.
11521   *
11522   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11523   * @copyright 2019 Bas Brands <bas@moodle.com>
11524   */
11525  class admin_setting_configthemepreset extends admin_setting_configselect {
11526  
11527      /** @var string The name of the theme to check for */
11528      private $themename;
11529  
11530      /**
11531       * Constructor
11532       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
11533       * or 'myplugin/mysetting' for ones in config_plugins.
11534       * @param string $visiblename localised
11535       * @param string $description long localised info
11536       * @param string|int $defaultsetting
11537       * @param array $choices array of $value=>$label for each selection
11538       * @param string $themename name of theme to check presets for.
11539       */
11540      public function __construct($name, $visiblename, $description, $defaultsetting, $choices, $themename) {
11541          $this->themename = $themename;
11542          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
11543      }
11544  
11545      /**
11546       * Write settings if validated
11547       *
11548       * @param string $data
11549       * @return string
11550       */
11551      public function write_setting($data) {
11552          $validated = $this->validate($data);
11553          if ($validated !== true) {
11554              return $validated;
11555          }
11556          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
11557      }
11558  
11559      /**
11560       * Validate the preset file to ensure its parsable.
11561       *
11562       * @param string $data The preset file chosen.
11563       * @return mixed bool true for success or string:error on failure.
11564       */
11565      public function validate($data) {
11566  
11567          if (in_array($data, ['default.scss', 'plain.scss'])) {
11568              return true;
11569          }
11570  
11571          $fs = get_file_storage();
11572          $theme = theme_config::load($this->themename);
11573          $context = context_system::instance();
11574  
11575          // If the preset has not changed there is no need to validate it.
11576          if ($theme->settings->preset == $data) {
11577              return true;
11578          }
11579  
11580          if ($presetfile = $fs->get_file($context->id, 'theme_' . $this->themename, 'preset', 0, '/', $data)) {
11581              // This operation uses a lot of resources.
11582              raise_memory_limit(MEMORY_EXTRA);
11583              core_php_time_limit::raise(300);
11584  
11585              // TODO: MDL-62757 When changing anything in this method please do not forget to check
11586              // if the get_css_content_from_scss() method in class theme_config needs updating too.
11587  
11588              $compiler = new core_scss();
11589              $compiler->prepend_raw_scss($theme->get_pre_scss_code());
11590              $compiler->append_raw_scss($presetfile->get_content());
11591              if ($scssproperties = $theme->get_scss_property()) {
11592                  $compiler->setImportPaths($scssproperties[0]);
11593              }
11594              $compiler->append_raw_scss($theme->get_extra_scss_code());
11595  
11596              try {
11597                  $compiler->to_css();
11598              } catch (Exception $e) {
11599                  return get_string('invalidthemepreset', 'admin', $e->getMessage());
11600              }
11601  
11602              // Try to save memory.
11603              $compiler = null;
11604              unset($compiler);
11605          }
11606  
11607          return true;
11608      }
11609  }
11610  
11611  /**
11612   * Selection of plugins that can work as H5P libraries handlers
11613   *
11614   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11615   * @copyright 2020 Sara Arjona <sara@moodle.com>
11616   */
11617  class admin_settings_h5plib_handler_select extends admin_setting_configselect {
11618  
11619      /**
11620       * Constructor
11621       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11622       *        for ones in config_plugins.
11623       * @param string $visiblename localised
11624       * @param string $description long localised info
11625       * @param string $defaultsetting
11626       */
11627      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11628          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11629      }
11630  
11631      /**
11632       * Lazy-load the available choices for the select box
11633       */
11634      public function load_choices() {
11635          if (during_initial_install()) {
11636              return false;
11637          }
11638          if (is_array($this->choices)) {
11639              return true;
11640          }
11641  
11642          $this->choices = \core_h5p\local\library\autoloader::get_all_handlers();
11643          foreach ($this->choices as $name => $class) {
11644              $this->choices[$name] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11645                  ['name' => new lang_string('pluginname', $name), 'component' => $name]);
11646          }
11647  
11648          return true;
11649      }
11650  }