Search moodle.org's
Developer Documentation

See Release Notes

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

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