Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/lib/ -> adminlib.php (source)

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Functions and classes used during installation, upgrades and for admin settings.
  19   *
  20   *  ADMIN SETTINGS TREE INTRODUCTION
  21   *
  22   *  This file performs the following tasks:
  23   *   -it defines the necessary objects and interfaces to build the Moodle
  24   *    admin hierarchy
  25   *   -it defines the admin_externalpage_setup()
  26   *
  27   *  ADMIN_SETTING OBJECTS
  28   *
  29   *  Moodle settings are represented by objects that inherit from the admin_setting
  30   *  class. These objects encapsulate how to read a setting, how to write a new value
  31   *  to a setting, and how to appropriately display the HTML to modify the setting.
  32   *
  33   *  ADMIN_SETTINGPAGE OBJECTS
  34   *
  35   *  The admin_setting objects are then grouped into admin_settingpages. The latter
  36   *  appear in the Moodle admin tree block. All interaction with admin_settingpage
  37   *  objects is handled by the admin/settings.php file.
  38   *
  39   *  ADMIN_EXTERNALPAGE OBJECTS
  40   *
  41   *  There are some settings in Moodle that are too complex to (efficiently) handle
  42   *  with admin_settingpages. (Consider, for example, user management and displaying
  43   *  lists of users.) In this case, we use the admin_externalpage object. This object
  44   *  places a link to an external PHP file in the admin tree block.
  45   *
  46   *  If you're using an admin_externalpage object for some settings, you can take
  47   *  advantage of the admin_externalpage_* functions. For example, suppose you wanted
  48   *  to add a foo.php file into admin. First off, you add the following line to
  49   *  admin/settings/first.php (at the end of the file) or to some other file in
  50   *  admin/settings:
  51   * <code>
  52   *     $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
  53   *         $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
  54   * </code>
  55   *
  56   *  Next, in foo.php, your file structure would resemble the following:
  57   * <code>
  58   *         require(__DIR__.'/../../config.php');
  59   *         require_once($CFG->libdir.'/adminlib.php');
  60   *         admin_externalpage_setup('foo');
  61   *         // functionality like processing form submissions goes here
  62   *         echo $OUTPUT->header();
  63   *         // your HTML goes here
  64   *         echo $OUTPUT->footer();
  65   * </code>
  66   *
  67   *  The admin_externalpage_setup() function call ensures the user is logged in,
  68   *  and makes sure that they have the proper role permission to access the page.
  69   *  It also configures all $PAGE properties needed for navigation.
  70   *
  71   *  ADMIN_CATEGORY OBJECTS
  72   *
  73   *  Above and beyond all this, we have admin_category objects. These objects
  74   *  appear as folders in the admin tree block. They contain admin_settingpage's,
  75   *  admin_externalpage's, and other admin_category's.
  76   *
  77   *  OTHER NOTES
  78   *
  79   *  admin_settingpage's, admin_externalpage's, and admin_category's all inherit
  80   *  from part_of_admin_tree (a pseudointerface). This interface insists that
  81   *  a class has a check_access method for access permissions, a locate method
  82   *  used to find a specific node in the admin tree and find parent path.
  83   *
  84   *  admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
  85   *  interface ensures that the class implements a recursive add function which
  86   *  accepts a part_of_admin_tree object and searches for the proper place to
  87   *  put it. parentable_part_of_admin_tree implies part_of_admin_tree.
  88   *
  89   *  Please note that the $this->name field of any part_of_admin_tree must be
  90   *  UNIQUE throughout the ENTIRE admin tree.
  91   *
  92   *  The $this->name field of an admin_setting object (which is *not* part_of_
  93   *  admin_tree) must be unique on the respective admin_settingpage where it is
  94   *  used.
  95   *
  96   * Original author: Vincenzo K. Marcovecchio
  97   * Maintainer:      Petr Skoda
  98   *
  99   * @package    core
 100   * @subpackage admin
 101   * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
 102   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 103   */
 104  
 105  defined('MOODLE_INTERNAL') || die();
 106  
 107  /// Add libraries
 108  require_once($CFG->libdir.'/ddllib.php');
 109  require_once($CFG->libdir.'/xmlize.php');
 110  require_once($CFG->libdir.'/messagelib.php');
 111  
 112  define('INSECURE_DATAROOT_WARNING', 1);
 113  define('INSECURE_DATAROOT_ERROR', 2);
 114  
 115  /**
 116   * Automatically clean-up all plugin data and remove the plugin DB tables
 117   *
 118   * NOTE: do not call directly, use new /admin/plugins.php?uninstall=component instead!
 119   *
 120   * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
 121   * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
 122   * @uses global $OUTPUT to produce notices and other messages
 123   * @return void
 124   */
 125  function uninstall_plugin($type, $name) {
 126      global $CFG, $DB, $OUTPUT;
 127  
 128      // This may take a long time.
 129      core_php_time_limit::raise();
 130  
 131      // Recursively uninstall all subplugins first.
 132      $subplugintypes = core_component::get_plugin_types_with_subplugins();
 133      if (isset($subplugintypes[$type])) {
 134          $base = core_component::get_plugin_directory($type, $name);
 135  
 136          $subpluginsfile = "{$base}/db/subplugins.json";
 137          if (file_exists($subpluginsfile)) {
 138              $subplugins = (array) json_decode(file_get_contents($subpluginsfile))->plugintypes;
 139          } else if (file_exists("{$base}/db/subplugins.php")) {
 140              debugging('Use of subplugins.php has been deprecated. ' .
 141                      'Please update your plugin to provide a subplugins.json file instead.',
 142                      DEBUG_DEVELOPER);
 143              $subplugins = [];
 144              include("{$base}/db/subplugins.php");
 145          }
 146  
 147          if (!empty($subplugins)) {
 148              foreach (array_keys($subplugins) as $subplugintype) {
 149                  $instances = core_component::get_plugin_list($subplugintype);
 150                  foreach ($instances as $subpluginname => $notusedpluginpath) {
 151                      uninstall_plugin($subplugintype, $subpluginname);
 152                  }
 153              }
 154          }
 155      }
 156  
 157      $component = $type . '_' . $name;  // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
 158  
 159      if ($type === 'mod') {
 160          $pluginname = $name;  // eg. 'forum'
 161          if (get_string_manager()->string_exists('modulename', $component)) {
 162              $strpluginname = get_string('modulename', $component);
 163          } else {
 164              $strpluginname = $component;
 165          }
 166  
 167      } else {
 168          $pluginname = $component;
 169          if (get_string_manager()->string_exists('pluginname', $component)) {
 170              $strpluginname = get_string('pluginname', $component);
 171          } else {
 172              $strpluginname = $component;
 173          }
 174      }
 175  
 176      echo $OUTPUT->heading($pluginname);
 177  
 178      // Delete all tag areas, collections and instances associated with this plugin.
 179      core_tag_area::uninstall($component);
 180  
 181      // Custom plugin uninstall.
 182      $plugindirectory = core_component::get_plugin_directory($type, $name);
 183      $uninstalllib = $plugindirectory . '/db/uninstall.php';
 184      if (file_exists($uninstalllib)) {
 185          require_once($uninstalllib);
 186          $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall';    // eg. 'xmldb_workshop_uninstall()'
 187          if (function_exists($uninstallfunction)) {
 188              // Do not verify result, let plugin complain if necessary.
 189              $uninstallfunction();
 190          }
 191      }
 192  
 193      // Specific plugin type cleanup.
 194      $plugininfo = core_plugin_manager::instance()->get_plugin_info($component);
 195      if ($plugininfo) {
 196          $plugininfo->uninstall_cleanup();
 197          core_plugin_manager::reset_caches();
 198      }
 199      $plugininfo = null;
 200  
 201      // perform clean-up task common for all the plugin/subplugin types
 202  
 203      //delete the web service functions and pre-built services
 204      require_once($CFG->dirroot.'/lib/externallib.php');
 205      external_delete_descriptions($component);
 206  
 207      // delete calendar events
 208      $DB->delete_records('event', array('modulename' => $pluginname));
 209      $DB->delete_records('event', ['component' => $component]);
 210  
 211      // Delete scheduled tasks.
 212      $DB->delete_records('task_adhoc', ['component' => $component]);
 213      $DB->delete_records('task_scheduled', array('component' => $component));
 214  
 215      // Delete Inbound Message datakeys.
 216      $DB->delete_records_select('messageinbound_datakeys',
 217              'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($component));
 218  
 219      // Delete Inbound Message handlers.
 220      $DB->delete_records('messageinbound_handlers', array('component' => $component));
 221  
 222      // delete all the logs
 223      $DB->delete_records('log', array('module' => $pluginname));
 224  
 225      // delete log_display information
 226      $DB->delete_records('log_display', array('component' => $component));
 227  
 228      // delete the module configuration records
 229      unset_all_config_for_plugin($component);
 230      if ($type === 'mod') {
 231          unset_all_config_for_plugin($pluginname);
 232      }
 233  
 234      // delete message provider
 235      message_provider_uninstall($component);
 236  
 237      // delete the plugin tables
 238      $xmldbfilepath = $plugindirectory . '/db/install.xml';
 239      drop_plugin_tables($component, $xmldbfilepath, false);
 240      if ($type === 'mod' or $type === 'block') {
 241          // non-frankenstyle table prefixes
 242          drop_plugin_tables($name, $xmldbfilepath, false);
 243      }
 244  
 245      // delete the capabilities that were defined by this module
 246      capabilities_cleanup($component);
 247  
 248      // Delete all remaining files in the filepool owned by the component.
 249      $fs = get_file_storage();
 250      $fs->delete_component_files($component);
 251  
 252      // Finally purge all caches.
 253      purge_all_caches();
 254  
 255      // Invalidate the hash used for upgrade detections.
 256      set_config('allversionshash', '');
 257  
 258      echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
 259  }
 260  
 261  /**
 262   * Returns the version of installed component
 263   *
 264   * @param string $component component name
 265   * @param string $source either 'disk' or 'installed' - where to get the version information from
 266   * @return string|bool version number or false if the component is not found
 267   */
 268  function get_component_version($component, $source='installed') {
 269      global $CFG, $DB;
 270  
 271      list($type, $name) = core_component::normalize_component($component);
 272  
 273      // moodle core or a core subsystem
 274      if ($type === 'core') {
 275          if ($source === 'installed') {
 276              if (empty($CFG->version)) {
 277                  return false;
 278              } else {
 279                  return $CFG->version;
 280              }
 281          } else {
 282              if (!is_readable($CFG->dirroot.'/version.php')) {
 283                  return false;
 284              } else {
 285                  $version = null; //initialize variable for IDEs
 286                  include($CFG->dirroot.'/version.php');
 287                  return $version;
 288              }
 289          }
 290      }
 291  
 292      // activity module
 293      if ($type === 'mod') {
 294          if ($source === 'installed') {
 295              if ($CFG->version < 2013092001.02) {
 296                  return $DB->get_field('modules', 'version', array('name'=>$name));
 297              } else {
 298                  return get_config('mod_'.$name, 'version');
 299              }
 300  
 301          } else {
 302              $mods = core_component::get_plugin_list('mod');
 303              if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
 304                  return false;
 305              } else {
 306                  $plugin = new stdClass();
 307                  $plugin->version = null;
 308                  $module = $plugin;
 309                  include($mods[$name].'/version.php');
 310                  return $plugin->version;
 311              }
 312          }
 313      }
 314  
 315      // block
 316      if ($type === 'block') {
 317          if ($source === 'installed') {
 318              if ($CFG->version < 2013092001.02) {
 319                  return $DB->get_field('block', 'version', array('name'=>$name));
 320              } else {
 321                  return get_config('block_'.$name, 'version');
 322              }
 323          } else {
 324              $blocks = core_component::get_plugin_list('block');
 325              if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
 326                  return false;
 327              } else {
 328                  $plugin = new stdclass();
 329                  include($blocks[$name].'/version.php');
 330                  return $plugin->version;
 331              }
 332          }
 333      }
 334  
 335      // all other plugin types
 336      if ($source === 'installed') {
 337          return get_config($type.'_'.$name, 'version');
 338      } else {
 339          $plugins = core_component::get_plugin_list($type);
 340          if (empty($plugins[$name])) {
 341              return false;
 342          } else {
 343              $plugin = new stdclass();
 344              include($plugins[$name].'/version.php');
 345              return $plugin->version;
 346          }
 347      }
 348  }
 349  
 350  /**
 351   * Delete all plugin tables
 352   *
 353   * @param string $name Name of plugin, used as table prefix
 354   * @param string $file Path to install.xml file
 355   * @param bool $feedback defaults to true
 356   * @return bool Always returns true
 357   */
 358  function drop_plugin_tables($name, $file, $feedback=true) {
 359      global $CFG, $DB;
 360  
 361      // first try normal delete
 362      if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
 363          return true;
 364      }
 365  
 366      // then try to find all tables that start with name and are not in any xml file
 367      $used_tables = get_used_table_names();
 368  
 369      $tables = $DB->get_tables();
 370  
 371      /// Iterate over, fixing id fields as necessary
 372      foreach ($tables as $table) {
 373          if (in_array($table, $used_tables)) {
 374              continue;
 375          }
 376  
 377          if (strpos($table, $name) !== 0) {
 378              continue;
 379          }
 380  
 381          // found orphan table --> delete it
 382          if ($DB->get_manager()->table_exists($table)) {
 383              $xmldb_table = new xmldb_table($table);
 384              $DB->get_manager()->drop_table($xmldb_table);
 385          }
 386      }
 387  
 388      return true;
 389  }
 390  
 391  /**
 392   * Returns names of all known tables == tables that moodle knows about.
 393   *
 394   * @return array Array of lowercase table names
 395   */
 396  function get_used_table_names() {
 397      $table_names = array();
 398      $dbdirs = get_db_directories();
 399  
 400      foreach ($dbdirs as $dbdir) {
 401          $file = $dbdir.'/install.xml';
 402  
 403          $xmldb_file = new xmldb_file($file);
 404  
 405          if (!$xmldb_file->fileExists()) {
 406              continue;
 407          }
 408  
 409          $loaded    = $xmldb_file->loadXMLStructure();
 410          $structure = $xmldb_file->getStructure();
 411  
 412          if ($loaded and $tables = $structure->getTables()) {
 413              foreach($tables as $table) {
 414                  $table_names[] = strtolower($table->getName());
 415              }
 416          }
 417      }
 418  
 419      return $table_names;
 420  }
 421  
 422  /**
 423   * Returns list of all directories where we expect install.xml files
 424   * @return array Array of paths
 425   */
 426  function get_db_directories() {
 427      global $CFG;
 428  
 429      $dbdirs = array();
 430  
 431      /// First, the main one (lib/db)
 432      $dbdirs[] = $CFG->libdir.'/db';
 433  
 434      /// Then, all the ones defined by core_component::get_plugin_types()
 435      $plugintypes = core_component::get_plugin_types();
 436      foreach ($plugintypes as $plugintype => $pluginbasedir) {
 437          if ($plugins = core_component::get_plugin_list($plugintype)) {
 438              foreach ($plugins as $plugin => $plugindir) {
 439                  $dbdirs[] = $plugindir.'/db';
 440              }
 441          }
 442      }
 443  
 444      return $dbdirs;
 445  }
 446  
 447  /**
 448   * Try to obtain or release the cron lock.
 449   * @param string  $name  name of lock
 450   * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionally
 451   * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
 452   * @return bool true if lock obtained
 453   */
 454  function set_cron_lock($name, $until, $ignorecurrent=false) {
 455      global $DB;
 456      if (empty($name)) {
 457          debugging("Tried to get a cron lock for a null fieldname");
 458          return false;
 459      }
 460  
 461      // remove lock by force == remove from config table
 462      if (is_null($until)) {
 463          set_config($name, null);
 464          return true;
 465      }
 466  
 467      if (!$ignorecurrent) {
 468          // read value from db - other processes might have changed it
 469          $value = $DB->get_field('config', 'value', array('name'=>$name));
 470  
 471          if ($value and $value > time()) {
 472              //lock active
 473              return false;
 474          }
 475      }
 476  
 477      set_config($name, $until);
 478      return true;
 479  }
 480  
 481  /**
 482   * Test if and critical warnings are present
 483   * @return bool
 484   */
 485  function admin_critical_warnings_present() {
 486      global $SESSION;
 487  
 488      if (!has_capability('moodle/site:config', context_system::instance())) {
 489          return 0;
 490      }
 491  
 492      if (!isset($SESSION->admin_critical_warning)) {
 493          $SESSION->admin_critical_warning = 0;
 494          if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
 495              $SESSION->admin_critical_warning = 1;
 496          }
 497      }
 498  
 499      return $SESSION->admin_critical_warning;
 500  }
 501  
 502  /**
 503   * Detects if float supports at least 10 decimal digits
 504   *
 505   * Detects if float supports at least 10 decimal digits
 506   * and also if float-->string conversion works as expected.
 507   *
 508   * @return bool true if problem found
 509   */
 510  function is_float_problem() {
 511      $num1 = 2009010200.01;
 512      $num2 = 2009010200.02;
 513  
 514      return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
 515  }
 516  
 517  /**
 518   * Try to verify that dataroot is not accessible from web.
 519   *
 520   * Try to verify that dataroot is not accessible from web.
 521   * It is not 100% correct but might help to reduce number of vulnerable sites.
 522   * Protection from httpd.conf and .htaccess is not detected properly.
 523   *
 524   * @uses INSECURE_DATAROOT_WARNING
 525   * @uses INSECURE_DATAROOT_ERROR
 526   * @param bool $fetchtest try to test public access by fetching file, default false
 527   * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
 528   */
 529  function is_dataroot_insecure($fetchtest=false) {
 530      global $CFG;
 531  
 532      $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
 533  
 534      $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
 535      $rp = strrev(trim($rp, '/'));
 536      $rp = explode('/', $rp);
 537      foreach($rp as $r) {
 538          if (strpos($siteroot, '/'.$r.'/') === 0) {
 539              $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
 540          } else {
 541              break; // probably alias root
 542          }
 543      }
 544  
 545      $siteroot = strrev($siteroot);
 546      $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
 547  
 548      if (strpos($dataroot, $siteroot) !== 0) {
 549          return false;
 550      }
 551  
 552      if (!$fetchtest) {
 553          return INSECURE_DATAROOT_WARNING;
 554      }
 555  
 556      // now try all methods to fetch a test file using http protocol
 557  
 558      $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
 559      preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
 560      $httpdocroot = $matches[1];
 561      $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
 562      make_upload_directory('diag');
 563      $testfile = $CFG->dataroot.'/diag/public.txt';
 564      if (!file_exists($testfile)) {
 565          file_put_contents($testfile, 'test file, do not delete');
 566          @chmod($testfile, $CFG->filepermissions);
 567      }
 568      $teststr = trim(file_get_contents($testfile));
 569      if (empty($teststr)) {
 570      // hmm, strange
 571          return INSECURE_DATAROOT_WARNING;
 572      }
 573  
 574      $testurl = $datarooturl.'/diag/public.txt';
 575      if (extension_loaded('curl') and
 576          !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
 577          !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
 578          ($ch = @curl_init($testurl)) !== false) {
 579          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 580          curl_setopt($ch, CURLOPT_HEADER, false);
 581          $data = curl_exec($ch);
 582          if (!curl_errno($ch)) {
 583              $data = trim($data);
 584              if ($data === $teststr) {
 585                  curl_close($ch);
 586                  return INSECURE_DATAROOT_ERROR;
 587              }
 588          }
 589          curl_close($ch);
 590      }
 591  
 592      if ($data = @file_get_contents($testurl)) {
 593          $data = trim($data);
 594          if ($data === $teststr) {
 595              return INSECURE_DATAROOT_ERROR;
 596          }
 597      }
 598  
 599      preg_match('|https?://([^/]+)|i', $testurl, $matches);
 600      $sitename = $matches[1];
 601      $error = 0;
 602      if ($fp = @fsockopen($sitename, 80, $error)) {
 603          preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
 604          $localurl = $matches[1];
 605          $out = "GET $localurl HTTP/1.1\r\n";
 606          $out .= "Host: $sitename\r\n";
 607          $out .= "Connection: Close\r\n\r\n";
 608          fwrite($fp, $out);
 609          $data = '';
 610          $incoming = false;
 611          while (!feof($fp)) {
 612              if ($incoming) {
 613                  $data .= fgets($fp, 1024);
 614              } else if (@fgets($fp, 1024) === "\r\n") {
 615                      $incoming = true;
 616                  }
 617          }
 618          fclose($fp);
 619          $data = trim($data);
 620          if ($data === $teststr) {
 621              return INSECURE_DATAROOT_ERROR;
 622          }
 623      }
 624  
 625      return INSECURE_DATAROOT_WARNING;
 626  }
 627  
 628  /**
 629   * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
 630   */
 631  function enable_cli_maintenance_mode() {
 632      global $CFG, $SITE;
 633  
 634      if (file_exists("$CFG->dataroot/climaintenance.html")) {
 635          unlink("$CFG->dataroot/climaintenance.html");
 636      }
 637  
 638      if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
 639          $data = $CFG->maintenance_message;
 640          $data = bootstrap_renderer::early_error_content($data, null, null, null);
 641          $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
 642  
 643      } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
 644          $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
 645  
 646      } else {
 647          $data = get_string('sitemaintenance', 'admin');
 648          $data = bootstrap_renderer::early_error_content($data, null, null, null);
 649          $data = bootstrap_renderer::plain_page(get_string('sitemaintenancetitle', 'admin',
 650              format_string($SITE->fullname, true, ['context' => context_system::instance()])), $data);
 651      }
 652  
 653      file_put_contents("$CFG->dataroot/climaintenance.html", $data);
 654      chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
 655  }
 656  
 657  /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
 658  
 659  
 660  /**
 661   * Interface for anything appearing in the admin tree
 662   *
 663   * The interface that is implemented by anything that appears in the admin tree
 664   * block. It forces inheriting classes to define a method for checking user permissions
 665   * and methods for finding something in the admin tree.
 666   *
 667   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 668   */
 669  interface part_of_admin_tree {
 670  
 671  /**
 672   * Finds a named part_of_admin_tree.
 673   *
 674   * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
 675   * and not parentable_part_of_admin_tree, then this function should only check if
 676   * $this->name matches $name. If it does, it should return a reference to $this,
 677   * otherwise, it should return a reference to NULL.
 678   *
 679   * If a class inherits parentable_part_of_admin_tree, this method should be called
 680   * recursively on all child objects (assuming, of course, the parent object's name
 681   * doesn't match the search criterion).
 682   *
 683   * @param string $name The internal name of the part_of_admin_tree we're searching for.
 684   * @return mixed An object reference or a NULL reference.
 685   */
 686      public function locate($name);
 687  
 688      /**
 689       * Removes named part_of_admin_tree.
 690       *
 691       * @param string $name The internal name of the part_of_admin_tree we want to remove.
 692       * @return bool success.
 693       */
 694      public function prune($name);
 695  
 696      /**
 697       * Search using query
 698       * @param string $query
 699       * @return mixed array-object structure of found settings and pages
 700       */
 701      public function search($query);
 702  
 703      /**
 704       * Verifies current user's access to this part_of_admin_tree.
 705       *
 706       * Used to check if the current user has access to this part of the admin tree or
 707       * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
 708       * then this method is usually just a call to has_capability() in the site context.
 709       *
 710       * If a class inherits parentable_part_of_admin_tree, this method should return the
 711       * logical OR of the return of check_access() on all child objects.
 712       *
 713       * @return bool True if the user has access, false if she doesn't.
 714       */
 715      public function check_access();
 716  
 717      /**
 718       * Mostly useful for removing of some parts of the tree in admin tree block.
 719       *
 720       * @return True is hidden from normal list view
 721       */
 722      public function is_hidden();
 723  
 724      /**
 725       * Show we display Save button at the page bottom?
 726       * @return bool
 727       */
 728      public function show_save();
 729  }
 730  
 731  
 732  /**
 733   * Interface implemented by any part_of_admin_tree that has children.
 734   *
 735   * The interface implemented by any part_of_admin_tree that can be a parent
 736   * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
 737   * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
 738   * include an add method for adding other part_of_admin_tree objects as children.
 739   *
 740   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 741   */
 742  interface parentable_part_of_admin_tree extends part_of_admin_tree {
 743  
 744  /**
 745   * Adds a part_of_admin_tree object to the admin tree.
 746   *
 747   * Used to add a part_of_admin_tree object to this object or a child of this
 748   * object. $something should only be added if $destinationname matches
 749   * $this->name. If it doesn't, add should be called on child objects that are
 750   * also parentable_part_of_admin_tree's.
 751   *
 752   * $something should be appended as the last child in the $destinationname. If the
 753   * $beforesibling is specified, $something should be prepended to it. If the given
 754   * sibling is not found, $something should be appended to the end of $destinationname
 755   * and a developer debugging message should be displayed.
 756   *
 757   * @param string $destinationname The internal name of the new parent for $something.
 758   * @param part_of_admin_tree $something The object to be added.
 759   * @return bool True on success, false on failure.
 760   */
 761      public function add($destinationname, $something, $beforesibling = null);
 762  
 763  }
 764  
 765  
 766  /**
 767   * The object used to represent folders (a.k.a. categories) in the admin tree block.
 768   *
 769   * Each admin_category object contains a number of part_of_admin_tree objects.
 770   *
 771   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 772   */
 773  class admin_category implements parentable_part_of_admin_tree {
 774  
 775      /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */
 776      protected $children;
 777      /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
 778      public $name;
 779      /** @var string The displayed name for this category. Usually obtained through get_string() */
 780      public $visiblename;
 781      /** @var bool Should this category be hidden in admin tree block? */
 782      public $hidden;
 783      /** @var mixed Either a string or an array or strings */
 784      public $path;
 785      /** @var mixed Either a string or an array or strings */
 786      public $visiblepath;
 787  
 788      /** @var array fast lookup category cache, all categories of one tree point to one cache */
 789      protected $category_cache;
 790  
 791      /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */
 792      protected $sort = false;
 793      /** @var bool If set to true children will be sorted in ascending order. */
 794      protected $sortasc = true;
 795      /** @var bool If set to true sub categories and pages will be split and then sorted.. */
 796      protected $sortsplit = true;
 797      /** @var bool $sorted True if the children have been sorted and don't need resorting */
 798      protected $sorted = false;
 799  
 800      /**
 801       * Constructor for an empty admin category
 802       *
 803       * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
 804       * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
 805       * @param bool $hidden hide category in admin tree block, defaults to false
 806       */
 807      public function __construct($name, $visiblename, $hidden=false) {
 808          $this->children    = array();
 809          $this->name        = $name;
 810          $this->visiblename = $visiblename;
 811          $this->hidden      = $hidden;
 812      }
 813  
 814      /**
 815       * Get the URL to view this page.
 816       *
 817       * @return moodle_url
 818       */
 819      public function get_settings_page_url(): moodle_url {
 820          return new moodle_url(
 821              '/admin/category.php',
 822              [
 823                  'category' => $this->name,
 824              ]
 825          );
 826      }
 827  
 828      /**
 829       * Returns a reference to the part_of_admin_tree object with internal name $name.
 830       *
 831       * @param string $name The internal name of the object we want.
 832       * @param bool $findpath initialize path and visiblepath arrays
 833       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
 834       *                  defaults to false
 835       */
 836      public function locate($name, $findpath=false) {
 837          if (!isset($this->category_cache[$this->name])) {
 838              // somebody much have purged the cache
 839              $this->category_cache[$this->name] = $this;
 840          }
 841  
 842          if ($this->name == $name) {
 843              if ($findpath) {
 844                  $this->visiblepath[] = $this->visiblename;
 845                  $this->path[]        = $this->name;
 846              }
 847              return $this;
 848          }
 849  
 850          // quick category lookup
 851          if (!$findpath and isset($this->category_cache[$name])) {
 852              return $this->category_cache[$name];
 853          }
 854  
 855          $return = NULL;
 856          foreach($this->children as $childid=>$unused) {
 857              if ($return = $this->children[$childid]->locate($name, $findpath)) {
 858                  break;
 859              }
 860          }
 861  
 862          if (!is_null($return) and $findpath) {
 863              $return->visiblepath[] = $this->visiblename;
 864              $return->path[]        = $this->name;
 865          }
 866  
 867          return $return;
 868      }
 869  
 870      /**
 871       * Search using query
 872       *
 873       * @param string query
 874       * @return mixed array-object structure of found settings and pages
 875       */
 876      public function search($query) {
 877          $result = array();
 878          foreach ($this->get_children() as $child) {
 879              $subsearch = $child->search($query);
 880              if (!is_array($subsearch)) {
 881                  debugging('Incorrect search result from '.$child->name);
 882                  continue;
 883              }
 884              $result = array_merge($result, $subsearch);
 885          }
 886          return $result;
 887      }
 888  
 889      /**
 890       * Removes part_of_admin_tree object with internal name $name.
 891       *
 892       * @param string $name The internal name of the object we want to remove.
 893       * @return bool success
 894       */
 895      public function prune($name) {
 896  
 897          if ($this->name == $name) {
 898              return false;  //can not remove itself
 899          }
 900  
 901          foreach($this->children as $precedence => $child) {
 902              if ($child->name == $name) {
 903                  // clear cache and delete self
 904                  while($this->category_cache) {
 905                      // delete the cache, but keep the original array address
 906                      array_pop($this->category_cache);
 907                  }
 908                  unset($this->children[$precedence]);
 909                  return true;
 910              } else if ($this->children[$precedence]->prune($name)) {
 911                  return true;
 912              }
 913          }
 914          return false;
 915      }
 916  
 917      /**
 918       * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
 919       *
 920       * By default the new part of the tree is appended as the last child of the parent. You
 921       * can specify a sibling node that the new part should be prepended to. If the given
 922       * sibling is not found, the part is appended to the end (as it would be by default) and
 923       * a developer debugging message is displayed.
 924       *
 925       * @throws coding_exception if the $beforesibling is empty string or is not string at all.
 926       * @param string $destinationame The internal name of the immediate parent that we want for $something.
 927       * @param mixed $something A part_of_admin_tree or setting instance to be added.
 928       * @param string $beforesibling The name of the parent's child the $something should be prepended to.
 929       * @return bool True if successfully added, false if $something can not be added.
 930       */
 931      public function add($parentname, $something, $beforesibling = null) {
 932          global $CFG;
 933  
 934          $parent = $this->locate($parentname);
 935          if (is_null($parent)) {
 936              debugging('parent does not exist!');
 937              return false;
 938          }
 939  
 940          if ($something instanceof part_of_admin_tree) {
 941              if (!($parent instanceof parentable_part_of_admin_tree)) {
 942                  debugging('error - parts of tree can be inserted only into parentable parts');
 943                  return false;
 944              }
 945              if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
 946                  // The name of the node is already used, simply warn the developer that this should not happen.
 947                  // It is intentional to check for the debug level before performing the check.
 948                  debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
 949              }
 950              if (is_null($beforesibling)) {
 951                  // Append $something as the parent's last child.
 952                  $parent->children[] = $something;
 953              } else {
 954                  if (!is_string($beforesibling) or trim($beforesibling) === '') {
 955                      throw new coding_exception('Unexpected value of the beforesibling parameter');
 956                  }
 957                  // Try to find the position of the sibling.
 958                  $siblingposition = null;
 959                  foreach ($parent->children as $childposition => $child) {
 960                      if ($child->name === $beforesibling) {
 961                          $siblingposition = $childposition;
 962                          break;
 963                      }
 964                  }
 965                  if (is_null($siblingposition)) {
 966                      debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
 967                      $parent->children[] = $something;
 968                  } else {
 969                      $parent->children = array_merge(
 970                          array_slice($parent->children, 0, $siblingposition),
 971                          array($something),
 972                          array_slice($parent->children, $siblingposition)
 973                      );
 974                  }
 975              }
 976              if ($something instanceof admin_category) {
 977                  if (isset($this->category_cache[$something->name])) {
 978                      debugging('Duplicate admin category name: '.$something->name);
 979                  } else {
 980                      $this->category_cache[$something->name] = $something;
 981                      $something->category_cache =& $this->category_cache;
 982                      foreach ($something->children as $child) {
 983                          // just in case somebody already added subcategories
 984                          if ($child instanceof admin_category) {
 985                              if (isset($this->category_cache[$child->name])) {
 986                                  debugging('Duplicate admin category name: '.$child->name);
 987                              } else {
 988                                  $this->category_cache[$child->name] = $child;
 989                                  $child->category_cache =& $this->category_cache;
 990                              }
 991                          }
 992                      }
 993                  }
 994              }
 995              return true;
 996  
 997          } else {
 998              debugging('error - can not add this element');
 999              return false;
1000          }
1001  
1002      }
1003  
1004      /**
1005       * Checks if the user has access to anything in this category.
1006       *
1007       * @return bool True if the user has access to at least one child in this category, false otherwise.
1008       */
1009      public function check_access() {
1010          foreach ($this->children as $child) {
1011              if ($child->check_access()) {
1012                  return true;
1013              }
1014          }
1015          return false;
1016      }
1017  
1018      /**
1019       * Is this category hidden in admin tree block?
1020       *
1021       * @return bool True if hidden
1022       */
1023      public function is_hidden() {
1024          return $this->hidden;
1025      }
1026  
1027      /**
1028       * Show we display Save button at the page bottom?
1029       * @return bool
1030       */
1031      public function show_save() {
1032          foreach ($this->children as $child) {
1033              if ($child->show_save()) {
1034                  return true;
1035              }
1036          }
1037          return false;
1038      }
1039  
1040      /**
1041       * Sets sorting on this category.
1042       *
1043       * Please note this function doesn't actually do the sorting.
1044       * It can be called anytime.
1045       * Sorting occurs when the user calls get_children.
1046       * Code using the children array directly won't see the sorted results.
1047       *
1048       * @param bool $sort If set to true children will be sorted, if false they won't be.
1049       * @param bool $asc If true sorting will be ascending, otherwise descending.
1050       * @param bool $split If true we sort pages and sub categories separately.
1051       */
1052      public function set_sorting($sort, $asc = true, $split = true) {
1053          $this->sort = (bool)$sort;
1054          $this->sortasc = (bool)$asc;
1055          $this->sortsplit = (bool)$split;
1056      }
1057  
1058      /**
1059       * Returns the children associated with this category.
1060       *
1061       * @return part_of_admin_tree[]
1062       */
1063      public function get_children() {
1064          // If we should sort and it hasn't already been sorted.
1065          if ($this->sort && !$this->sorted) {
1066              if ($this->sortsplit) {
1067                  $categories = array();
1068                  $pages = array();
1069                  foreach ($this->children as $child) {
1070                      if ($child instanceof admin_category) {
1071                          $categories[] = $child;
1072                      } else {
1073                          $pages[] = $child;
1074                      }
1075                  }
1076                  core_collator::asort_objects_by_property($categories, 'visiblename');
1077                  core_collator::asort_objects_by_property($pages, 'visiblename');
1078                  if (!$this->sortasc) {
1079                      $categories = array_reverse($categories);
1080                      $pages = array_reverse($pages);
1081                  }
1082                  $this->children = array_merge($pages, $categories);
1083              } else {
1084                  core_collator::asort_objects_by_property($this->children, 'visiblename');
1085                  if (!$this->sortasc) {
1086                      $this->children = array_reverse($this->children);
1087                  }
1088              }
1089              $this->sorted = true;
1090          }
1091          return $this->children;
1092      }
1093  
1094      /**
1095       * Magically gets a property from this object.
1096       *
1097       * @param $property
1098       * @return part_of_admin_tree[]
1099       * @throws coding_exception
1100       */
1101      public function __get($property) {
1102          if ($property === 'children') {
1103              return $this->get_children();
1104          }
1105          throw new coding_exception('Invalid property requested.');
1106      }
1107  
1108      /**
1109       * Magically sets a property against this object.
1110       *
1111       * @param string $property
1112       * @param mixed $value
1113       * @throws coding_exception
1114       */
1115      public function __set($property, $value) {
1116          if ($property === 'children') {
1117              $this->sorted = false;
1118              $this->children = $value;
1119          } else {
1120              throw new coding_exception('Invalid property requested.');
1121          }
1122      }
1123  
1124      /**
1125       * Checks if an inaccessible property is set.
1126       *
1127       * @param string $property
1128       * @return bool
1129       * @throws coding_exception
1130       */
1131      public function __isset($property) {
1132          if ($property === 'children') {
1133              return isset($this->children);
1134          }
1135          throw new coding_exception('Invalid property requested.');
1136      }
1137  }
1138  
1139  
1140  /**
1141   * Root of admin settings tree, does not have any parent.
1142   *
1143   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1144   */
1145  class admin_root extends admin_category {
1146  /** @var array List of errors */
1147      public $errors;
1148      /** @var string search query */
1149      public $search;
1150      /** @var bool full tree flag - true means all settings required, false only pages required */
1151      public $fulltree;
1152      /** @var bool flag indicating loaded tree */
1153      public $loaded;
1154      /** @var mixed site custom defaults overriding defaults in settings files*/
1155      public $custom_defaults;
1156  
1157      /**
1158       * @param bool $fulltree true means all settings required,
1159       *                            false only pages required
1160       */
1161      public function __construct($fulltree) {
1162          global $CFG;
1163  
1164          parent::__construct('root', get_string('administration'), false);
1165          $this->errors   = array();
1166          $this->search   = '';
1167          $this->fulltree = $fulltree;
1168          $this->loaded   = false;
1169  
1170          $this->category_cache = array();
1171  
1172          // load custom defaults if found
1173          $this->custom_defaults = null;
1174          $defaultsfile = "$CFG->dirroot/local/defaults.php";
1175          if (is_readable($defaultsfile)) {
1176              $defaults = array();
1177              include($defaultsfile);
1178              if (is_array($defaults) and count($defaults)) {
1179                  $this->custom_defaults = $defaults;
1180              }
1181          }
1182      }
1183  
1184      /**
1185       * Empties children array, and sets loaded to false
1186       *
1187       * @param bool $requirefulltree
1188       */
1189      public function purge_children($requirefulltree) {
1190          $this->children = array();
1191          $this->fulltree = ($requirefulltree || $this->fulltree);
1192          $this->loaded   = false;
1193          //break circular dependencies - this helps PHP 5.2
1194          while($this->category_cache) {
1195              array_pop($this->category_cache);
1196          }
1197          $this->category_cache = array();
1198      }
1199  }
1200  
1201  
1202  /**
1203   * Links external PHP pages into the admin tree.
1204   *
1205   * See detailed usage example at the top of this document (adminlib.php)
1206   *
1207   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1208   */
1209  class admin_externalpage implements part_of_admin_tree {
1210  
1211      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1212      public $name;
1213  
1214      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1215      public $visiblename;
1216  
1217      /** @var string The external URL that we should link to when someone requests this external page. */
1218      public $url;
1219  
1220      /** @var array The role capability/permission a user must have to access this external page. */
1221      public $req_capability;
1222  
1223      /** @var object The context in which capability/permission should be checked, default is site context. */
1224      public $context;
1225  
1226      /** @var bool hidden in admin tree block. */
1227      public $hidden;
1228  
1229      /** @var mixed either string or array of string */
1230      public $path;
1231  
1232      /** @var array list of visible names of page parents */
1233      public $visiblepath;
1234  
1235      /**
1236       * Constructor for adding an external page into the admin tree.
1237       *
1238       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1239       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1240       * @param string $url The external URL that we should link to when someone requests this external page.
1241       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1242       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1243       * @param stdClass $context The context the page relates to. Not sure what happens
1244       *      if you specify something other than system or front page. Defaults to system.
1245       */
1246      public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1247          $this->name        = $name;
1248          $this->visiblename = $visiblename;
1249          $this->url         = $url;
1250          if (is_array($req_capability)) {
1251              $this->req_capability = $req_capability;
1252          } else {
1253              $this->req_capability = array($req_capability);
1254          }
1255          $this->hidden = $hidden;
1256          $this->context = $context;
1257      }
1258  
1259      /**
1260       * Returns a reference to the part_of_admin_tree object with internal name $name.
1261       *
1262       * @param string $name The internal name of the object we want.
1263       * @param bool $findpath defaults to false
1264       * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1265       */
1266      public function locate($name, $findpath=false) {
1267          if ($this->name == $name) {
1268              if ($findpath) {
1269                  $this->visiblepath = array($this->visiblename);
1270                  $this->path        = array($this->name);
1271              }
1272              return $this;
1273          } else {
1274              $return = NULL;
1275              return $return;
1276          }
1277      }
1278  
1279      /**
1280       * This function always returns false, required function by interface
1281       *
1282       * @param string $name
1283       * @return false
1284       */
1285      public function prune($name) {
1286          return false;
1287      }
1288  
1289      /**
1290       * Search using query
1291       *
1292       * @param string $query
1293       * @return mixed array-object structure of found settings and pages
1294       */
1295      public function search($query) {
1296          $found = false;
1297          if (strpos(strtolower($this->name), $query) !== false) {
1298              $found = true;
1299          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1300                  $found = true;
1301              }
1302          if ($found) {
1303              $result = new stdClass();
1304              $result->page     = $this;
1305              $result->settings = array();
1306              return array($this->name => $result);
1307          } else {
1308              return array();
1309          }
1310      }
1311  
1312      /**
1313       * Determines if the current user has access to this external page based on $this->req_capability.
1314       *
1315       * @return bool True if user has access, false otherwise.
1316       */
1317      public function check_access() {
1318          global $CFG;
1319          $context = empty($this->context) ? context_system::instance() : $this->context;
1320          foreach($this->req_capability as $cap) {
1321              if (has_capability($cap, $context)) {
1322                  return true;
1323              }
1324          }
1325          return false;
1326      }
1327  
1328      /**
1329       * Is this external page hidden in admin tree block?
1330       *
1331       * @return bool True if hidden
1332       */
1333      public function is_hidden() {
1334          return $this->hidden;
1335      }
1336  
1337      /**
1338       * Show we display Save button at the page bottom?
1339       * @return bool
1340       */
1341      public function show_save() {
1342          return false;
1343      }
1344  }
1345  
1346  /**
1347   * Used to store details of the dependency between two settings elements.
1348   *
1349   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1350   * @copyright 2017 Davo Smith, Synergy Learning
1351   */
1352  class admin_settingdependency {
1353      /** @var string the name of the setting to be shown/hidden */
1354      public $settingname;
1355      /** @var string the setting this is dependent on */
1356      public $dependenton;
1357      /** @var string the condition to show/hide the element */
1358      public $condition;
1359      /** @var string the value to compare against */
1360      public $value;
1361  
1362      /** @var string[] list of valid conditions */
1363      private static $validconditions = ['checked', 'notchecked', 'noitemselected', 'eq', 'neq', 'in'];
1364  
1365      /**
1366       * admin_settingdependency constructor.
1367       * @param string $settingname
1368       * @param string $dependenton
1369       * @param string $condition
1370       * @param string $value
1371       * @throws \coding_exception
1372       */
1373      public function __construct($settingname, $dependenton, $condition, $value) {
1374          $this->settingname = $this->parse_name($settingname);
1375          $this->dependenton = $this->parse_name($dependenton);
1376          $this->condition = $condition;
1377          $this->value = $value;
1378  
1379          if (!in_array($this->condition, self::$validconditions)) {
1380              throw new coding_exception("Invalid condition '$condition'");
1381          }
1382      }
1383  
1384      /**
1385       * Convert the setting name into the form field name.
1386       * @param string $name
1387       * @return string
1388       */
1389      private function parse_name($name) {
1390          $bits = explode('/', $name);
1391          $name = array_pop($bits);
1392          $plugin = '';
1393          if ($bits) {
1394              $plugin = array_pop($bits);
1395              if ($plugin === 'moodle') {
1396                  $plugin = '';
1397              }
1398          }
1399          return 's_'.$plugin.'_'.$name;
1400      }
1401  
1402      /**
1403       * Gather together all the dependencies in a format suitable for initialising javascript
1404       * @param admin_settingdependency[] $dependencies
1405       * @return array
1406       */
1407      public static function prepare_for_javascript($dependencies) {
1408          $result = [];
1409          foreach ($dependencies as $d) {
1410              if (!isset($result[$d->dependenton])) {
1411                  $result[$d->dependenton] = [];
1412              }
1413              if (!isset($result[$d->dependenton][$d->condition])) {
1414                  $result[$d->dependenton][$d->condition] = [];
1415              }
1416              if (!isset($result[$d->dependenton][$d->condition][$d->value])) {
1417                  $result[$d->dependenton][$d->condition][$d->value] = [];
1418              }
1419              $result[$d->dependenton][$d->condition][$d->value][] = $d->settingname;
1420          }
1421          return $result;
1422      }
1423  }
1424  
1425  /**
1426   * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1427   *
1428   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1429   */
1430  class admin_settingpage implements part_of_admin_tree {
1431  
1432      /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1433      public $name;
1434  
1435      /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1436      public $visiblename;
1437  
1438      /** @var mixed An array of admin_setting objects that are part of this setting page. */
1439      public $settings;
1440  
1441      /** @var admin_settingdependency[] list of settings to hide when certain conditions are met */
1442      protected $dependencies = [];
1443  
1444      /** @var array The role capability/permission a user must have to access this external page. */
1445      public $req_capability;
1446  
1447      /** @var object The context in which capability/permission should be checked, default is site context. */
1448      public $context;
1449  
1450      /** @var bool hidden in admin tree block. */
1451      public $hidden;
1452  
1453      /** @var mixed string of paths or array of strings of paths */
1454      public $path;
1455  
1456      /** @var array list of visible names of page parents */
1457      public $visiblepath;
1458  
1459      /**
1460       * see admin_settingpage for details of this function
1461       *
1462       * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1463       * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1464       * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1465       * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1466       * @param stdClass $context The context the page relates to. Not sure what happens
1467       *      if you specify something other than system or front page. Defaults to system.
1468       */
1469      public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1470          $this->settings    = new stdClass();
1471          $this->name        = $name;
1472          $this->visiblename = $visiblename;
1473          if (is_array($req_capability)) {
1474              $this->req_capability = $req_capability;
1475          } else {
1476              $this->req_capability = array($req_capability);
1477          }
1478          $this->hidden      = $hidden;
1479          $this->context     = $context;
1480      }
1481  
1482      /**
1483       * see admin_category
1484       *
1485       * @param string $name
1486       * @param bool $findpath
1487       * @return mixed Object (this) if name ==  this->name, else returns null
1488       */
1489      public function locate($name, $findpath=false) {
1490          if ($this->name == $name) {
1491              if ($findpath) {
1492                  $this->visiblepath = array($this->visiblename);
1493                  $this->path        = array($this->name);
1494              }
1495              return $this;
1496          } else {
1497              $return = NULL;
1498              return $return;
1499          }
1500      }
1501  
1502      /**
1503       * Search string in settings page.
1504       *
1505       * @param string $query
1506       * @return array
1507       */
1508      public function search($query) {
1509          $found = array();
1510  
1511          foreach ($this->settings as $setting) {
1512              if ($setting->is_related($query)) {
1513                  $found[] = $setting;
1514              }
1515          }
1516  
1517          if ($found) {
1518              $result = new stdClass();
1519              $result->page     = $this;
1520              $result->settings = $found;
1521              return array($this->name => $result);
1522          }
1523  
1524          $found = false;
1525          if (strpos(strtolower($this->name), $query) !== false) {
1526              $found = true;
1527          } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1528                  $found = true;
1529              }
1530          if ($found) {
1531              $result = new stdClass();
1532              $result->page     = $this;
1533              $result->settings = array();
1534              return array($this->name => $result);
1535          } else {
1536              return array();
1537          }
1538      }
1539  
1540      /**
1541       * This function always returns false, required by interface
1542       *
1543       * @param string $name
1544       * @return bool Always false
1545       */
1546      public function prune($name) {
1547          return false;
1548      }
1549  
1550      /**
1551       * adds an admin_setting to this admin_settingpage
1552       *
1553       * not the same as add for admin_category. adds an admin_setting to this admin_settingpage. settings appear (on the settingpage) in the order in which they're added
1554       * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1555       *
1556       * @param object $setting is the admin_setting object you want to add
1557       * @return bool true if successful, false if not
1558       */
1559      public function add($setting) {
1560          if (!($setting instanceof admin_setting)) {
1561              debugging('error - not a setting instance');
1562              return false;
1563          }
1564  
1565          $name = $setting->name;
1566          if ($setting->plugin) {
1567              $name = $setting->plugin . $name;
1568          }
1569          $this->settings->{$name} = $setting;
1570          return true;
1571      }
1572  
1573      /**
1574       * Hide the named setting if the specified condition is matched.
1575       *
1576       * @param string $settingname
1577       * @param string $dependenton
1578       * @param string $condition
1579       * @param string $value
1580       */
1581      public function hide_if($settingname, $dependenton, $condition = 'notchecked', $value = '1') {
1582          $this->dependencies[] = new admin_settingdependency($settingname, $dependenton, $condition, $value);
1583  
1584          // Reformat the dependency name to the plugin | name format used in the display.
1585          $dependenton = str_replace('/', ' | ', $dependenton);
1586  
1587          // Let the setting know, so it can be displayed underneath.
1588          $findname = str_replace('/', '', $settingname);
1589          foreach ($this->settings as $name => $setting) {
1590              if ($name === $findname) {
1591                  $setting->add_dependent_on($dependenton);
1592              }
1593          }
1594      }
1595  
1596      /**
1597       * see admin_externalpage
1598       *
1599       * @return bool Returns true for yes false for no
1600       */
1601      public function check_access() {
1602          global $CFG;
1603          $context = empty($this->context) ? context_system::instance() : $this->context;
1604          foreach($this->req_capability as $cap) {
1605              if (has_capability($cap, $context)) {
1606                  return true;
1607              }
1608          }
1609          return false;
1610      }
1611  
1612      /**
1613       * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1614       * @return string Returns an XHTML string
1615       */
1616      public function output_html() {
1617          $adminroot = admin_get_root();
1618          $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1619          foreach($this->settings as $setting) {
1620              $fullname = $setting->get_full_name();
1621              if (array_key_exists($fullname, $adminroot->errors)) {
1622                  $data = $adminroot->errors[$fullname]->data;
1623              } else {
1624                  $data = $setting->get_setting();
1625                  // do not use defaults if settings not available - upgrade settings handles the defaults!
1626              }
1627              $return .= $setting->output_html($data);
1628          }
1629          $return .= '</fieldset>';
1630          return $return;
1631      }
1632  
1633      /**
1634       * Is this settings page hidden in admin tree block?
1635       *
1636       * @return bool True if hidden
1637       */
1638      public function is_hidden() {
1639          return $this->hidden;
1640      }
1641  
1642      /**
1643       * Show we display Save button at the page bottom?
1644       * @return bool
1645       */
1646      public function show_save() {
1647          foreach($this->settings as $setting) {
1648              if (empty($setting->nosave)) {
1649                  return true;
1650              }
1651          }
1652          return false;
1653      }
1654  
1655      /**
1656       * Should any of the settings on this page be shown / hidden based on conditions?
1657       * @return bool
1658       */
1659      public function has_dependencies() {
1660          return (bool)$this->dependencies;
1661      }
1662  
1663      /**
1664       * Format the setting show/hide conditions ready to initialise the page javascript
1665       * @return array
1666       */
1667      public function get_dependencies_for_javascript() {
1668          if (!$this->has_dependencies()) {
1669              return [];
1670          }
1671          return admin_settingdependency::prepare_for_javascript($this->dependencies);
1672      }
1673  }
1674  
1675  
1676  /**
1677   * Admin settings class. Only exists on setting pages.
1678   * Read & write happens at this level; no authentication.
1679   *
1680   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1681   */
1682  abstract class admin_setting {
1683      /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1684      public $name;
1685      /** @var string localised name */
1686      public $visiblename;
1687      /** @var string localised long description in Markdown format */
1688      public $description;
1689      /** @var mixed Can be string or array of string */
1690      public $defaultsetting;
1691      /** @var string */
1692      public $updatedcallback;
1693      /** @var mixed can be String or Null.  Null means main config table */
1694      public $plugin; // null means main config table
1695      /** @var bool true indicates this setting does not actually save anything, just information */
1696      public $nosave = false;
1697      /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1698      public $affectsmodinfo = false;
1699      /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */
1700      private $flags = array();
1701      /** @var bool Whether this field must be forced LTR. */
1702      private $forceltr = null;
1703      /** @var array list of other settings that may cause this setting to be hidden */
1704      private $dependenton = [];
1705      /** @var bool Whether this setting uses a custom form control */
1706      protected $customcontrol = false;
1707  
1708      /**
1709       * Constructor
1710       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1711       *                     or 'myplugin/mysetting' for ones in config_plugins.
1712       * @param string $visiblename localised name
1713       * @param string $description localised long description
1714       * @param mixed $defaultsetting string or array depending on implementation
1715       */
1716      public function __construct($name, $visiblename, $description, $defaultsetting) {
1717          $this->parse_setting_name($name);
1718          $this->visiblename    = $visiblename;
1719          $this->description    = $description;
1720          $this->defaultsetting = $defaultsetting;
1721      }
1722  
1723      /**
1724       * Generic function to add a flag to this admin setting.
1725       *
1726       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1727       * @param bool $default - The default for the flag
1728       * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name.
1729       * @param string $displayname - The display name for this flag. Used as a label next to the checkbox.
1730       */
1731      protected function set_flag_options($enabled, $default, $shortname, $displayname) {
1732          if (empty($this->flags[$shortname])) {
1733              $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname);
1734          } else {
1735              $this->flags[$shortname]->set_options($enabled, $default);
1736          }
1737      }
1738  
1739      /**
1740       * Set the enabled options flag on this admin setting.
1741       *
1742       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1743       * @param bool $default - The default for the flag
1744       */
1745      public function set_enabled_flag_options($enabled, $default) {
1746          $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin'));
1747      }
1748  
1749      /**
1750       * Set the advanced options flag on this admin setting.
1751       *
1752       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1753       * @param bool $default - The default for the flag
1754       */
1755      public function set_advanced_flag_options($enabled, $default) {
1756          $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced'));
1757      }
1758  
1759  
1760      /**
1761       * Set the locked options flag on this admin setting.
1762       *
1763       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1764       * @param bool $default - The default for the flag
1765       */
1766      public function set_locked_flag_options($enabled, $default) {
1767          $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
1768      }
1769  
1770      /**
1771       * Set the required options flag on this admin setting.
1772       *
1773       * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED.
1774       * @param bool $default - The default for the flag.
1775       */
1776      public function set_required_flag_options($enabled, $default) {
1777          $this->set_flag_options($enabled, $default, 'required', new lang_string('required', 'core_admin'));
1778      }
1779  
1780      /**
1781       * Is this option forced in config.php?
1782       *
1783       * @return bool
1784       */
1785      public function is_readonly(): bool {
1786          global $CFG;
1787  
1788          if (empty($this->plugin)) {
1789              if (array_key_exists($this->name, $CFG->config_php_settings)) {
1790                  return true;
1791              }
1792          } else {
1793              if (array_key_exists($this->plugin, $CFG->forced_plugin_settings)
1794                  and array_key_exists($this->name, $CFG->forced_plugin_settings[$this->plugin])) {
1795                  return true;
1796              }
1797          }
1798          return false;
1799      }
1800  
1801      /**
1802       * Get the currently saved value for a setting flag
1803       *
1804       * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting.
1805       * @return bool
1806       */
1807      public function get_setting_flag_value(admin_setting_flag $flag) {
1808          $value = $this->config_read($this->name . '_' . $flag->get_shortname());
1809          if (!isset($value)) {
1810              $value = $flag->get_default();
1811          }
1812  
1813          return !empty($value);
1814      }
1815  
1816      /**
1817       * Get the list of defaults for the flags on this setting.
1818       *
1819       * @param array of strings describing the defaults for this setting. This is appended to by this function.
1820       */
1821      public function get_setting_flag_defaults(& $defaults) {
1822          foreach ($this->flags as $flag) {
1823              if ($flag->is_enabled() && $flag->get_default()) {
1824                  $defaults[] = $flag->get_displayname();
1825              }
1826          }
1827      }
1828  
1829      /**
1830       * Output the input fields for the advanced and locked flags on this setting.
1831       *
1832       * @param bool $adv - The current value of the advanced flag.
1833       * @param bool $locked - The current value of the locked flag.
1834       * @return string $output - The html for the flags.
1835       */
1836      public function output_setting_flags() {
1837          $output = '';
1838  
1839          foreach ($this->flags as $flag) {
1840              if ($flag->is_enabled()) {
1841                  $output .= $flag->output_setting_flag($this);
1842              }
1843          }
1844  
1845          if (!empty($output)) {
1846              return html_writer::tag('span', $output, array('class' => 'adminsettingsflags'));
1847          }
1848          return $output;
1849      }
1850  
1851      /**
1852       * Write the values of the flags for this admin setting.
1853       *
1854       * @param array $data - The data submitted from the form or null to set the default value for new installs.
1855       * @return bool - true if successful.
1856       */
1857      public function write_setting_flags($data) {
1858          $result = true;
1859          foreach ($this->flags as $flag) {
1860              $result = $result && $flag->write_setting_flag($this, $data);
1861          }
1862          return $result;
1863      }
1864  
1865      /**
1866       * Set up $this->name and potentially $this->plugin
1867       *
1868       * Set up $this->name and possibly $this->plugin based on whether $name looks
1869       * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1870       * on the names, that is, output a developer debug warning if the name
1871       * contains anything other than [a-zA-Z0-9_]+.
1872       *
1873       * @param string $name the setting name passed in to the constructor.
1874       */
1875      private function parse_setting_name($name) {
1876          $bits = explode('/', $name);
1877          if (count($bits) > 2) {
1878              throw new moodle_exception('invalidadminsettingname', '', '', $name);
1879          }
1880          $this->name = array_pop($bits);
1881          if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1882              throw new moodle_exception('invalidadminsettingname', '', '', $name);
1883          }
1884          if (!empty($bits)) {
1885              $this->plugin = array_pop($bits);
1886              if ($this->plugin === 'moodle') {
1887                  $this->plugin = null;
1888              } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1889                      throw new moodle_exception('invalidadminsettingname', '', '', $name);
1890                  }
1891          }
1892      }
1893  
1894      /**
1895       * Returns the fullname prefixed by the plugin
1896       * @return string
1897       */
1898      public function get_full_name() {
1899          return 's_'.$this->plugin.'_'.$this->name;
1900      }
1901  
1902      /**
1903       * Returns the ID string based on plugin and name
1904       * @return string
1905       */
1906      public function get_id() {
1907          return 'id_s_'.$this->plugin.'_'.$this->name;
1908      }
1909  
1910      /**
1911       * @param bool $affectsmodinfo If true, changes to this setting will
1912       *   cause the course cache to be rebuilt
1913       */
1914      public function set_affects_modinfo($affectsmodinfo) {
1915          $this->affectsmodinfo = $affectsmodinfo;
1916      }
1917  
1918      /**
1919       * Returns the config if possible
1920       *
1921       * @return mixed returns config if successful else null
1922       */
1923      public function config_read($name) {
1924          global $CFG;
1925          if (!empty($this->plugin)) {
1926              $value = get_config($this->plugin, $name);
1927              return $value === false ? NULL : $value;
1928  
1929          } else {
1930              if (isset($CFG->$name)) {
1931                  return $CFG->$name;
1932              } else {
1933                  return NULL;
1934              }
1935          }
1936      }
1937  
1938      /**
1939       * Used to set a config pair and log change
1940       *
1941       * @param string $name
1942       * @param mixed $value Gets converted to string if not null
1943       * @return bool Write setting to config table
1944       */
1945      public function config_write($name, $value) {
1946          global $DB, $USER, $CFG;
1947  
1948          if ($this->nosave) {
1949              return true;
1950          }
1951  
1952          // make sure it is a real change
1953          $oldvalue = get_config($this->plugin, $name);
1954          $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1955          $value = is_null($value) ? null : (string)$value;
1956  
1957          if ($oldvalue === $value) {
1958              return true;
1959          }
1960  
1961          // store change
1962          set_config($name, $value, $this->plugin);
1963  
1964          // Some admin settings affect course modinfo
1965          if ($this->affectsmodinfo) {
1966              // Clear course cache for all courses
1967              rebuild_course_cache(0, true);
1968          }
1969  
1970          $this->add_to_config_log($name, $oldvalue, $value);
1971  
1972          return true; // BC only
1973      }
1974  
1975      /**
1976       * Log config changes if necessary.
1977       * @param string $name
1978       * @param string $oldvalue
1979       * @param string $value
1980       */
1981      protected function add_to_config_log($name, $oldvalue, $value) {
1982          add_to_config_log($name, $oldvalue, $value, $this->plugin);
1983      }
1984  
1985      /**
1986       * Returns current value of this setting
1987       * @return mixed array or string depending on instance, NULL means not set yet
1988       */
1989      public abstract function get_setting();
1990  
1991      /**
1992       * Returns default setting if exists
1993       * @return mixed array or string depending on instance; NULL means no default, user must supply
1994       */
1995      public function get_defaultsetting() {
1996          $adminroot =  admin_get_root(false, false);
1997          if (!empty($adminroot->custom_defaults)) {
1998              $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1999              if (isset($adminroot->custom_defaults[$plugin])) {
2000                  if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
2001                      return $adminroot->custom_defaults[$plugin][$this->name];
2002                  }
2003              }
2004          }
2005          return $this->defaultsetting;
2006      }
2007  
2008      /**
2009       * Store new setting
2010       *
2011       * @param mixed $data string or array, must not be NULL
2012       * @return string empty string if ok, string error message otherwise
2013       */
2014      public abstract function write_setting($data);
2015  
2016      /**
2017       * Return part of form with setting
2018       * This function should always be overwritten
2019       *
2020       * @param mixed $data array or string depending on setting
2021       * @param string $query
2022       * @return string
2023       */
2024      public function output_html($data, $query='') {
2025      // should be overridden
2026          return;
2027      }
2028  
2029      /**
2030       * Function called if setting updated - cleanup, cache reset, etc.
2031       * @param string $functionname Sets the function name
2032       * @return void
2033       */
2034      public function set_updatedcallback($functionname) {
2035          $this->updatedcallback = $functionname;
2036      }
2037  
2038      /**
2039       * Execute postupdatecallback if necessary.
2040       * @param mixed $original original value before write_setting()
2041       * @return bool true if changed, false if not.
2042       */
2043      public function post_write_settings($original) {
2044          // Comparison must work for arrays too.
2045          if (serialize($original) === serialize($this->get_setting())) {
2046              return false;
2047          }
2048  
2049          $callbackfunction = $this->updatedcallback;
2050          if (!empty($callbackfunction) and is_callable($callbackfunction)) {
2051              $callbackfunction($this->get_full_name());
2052          }
2053          return true;
2054      }
2055  
2056      /**
2057       * Is setting related to query text - used when searching
2058       * @param string $query
2059       * @return bool
2060       */
2061      public function is_related($query) {
2062          if (strpos(strtolower($this->name), $query) !== false) {
2063              return true;
2064          }
2065          if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
2066              return true;
2067          }
2068          if (strpos(core_text::strtolower($this->description), $query) !== false) {
2069              return true;
2070          }
2071          $current = $this->get_setting();
2072          if (!is_null($current)) {
2073              if (is_string($current)) {
2074                  if (strpos(core_text::strtolower($current), $query) !== false) {
2075                      return true;
2076                  }
2077              }
2078          }
2079          $default = $this->get_defaultsetting();
2080          if (!is_null($default)) {
2081              if (is_string($default)) {
2082                  if (strpos(core_text::strtolower($default), $query) !== false) {
2083                      return true;
2084                  }
2085              }
2086          }
2087          return false;
2088      }
2089  
2090      /**
2091       * Get whether this should be displayed in LTR mode.
2092       *
2093       * @return bool|null
2094       */
2095      public function get_force_ltr() {
2096          return $this->forceltr;
2097      }
2098  
2099      /**
2100       * Set whether to force LTR or not.
2101       *
2102       * @param bool $value True when forced, false when not force, null when unknown.
2103       */
2104      public function set_force_ltr($value) {
2105          $this->forceltr = $value;
2106      }
2107  
2108      /**
2109       * Add a setting to the list of those that could cause this one to be hidden
2110       * @param string $dependenton
2111       */
2112      public function add_dependent_on($dependenton) {
2113          $this->dependenton[] = $dependenton;
2114      }
2115  
2116      /**
2117       * Get a list of the settings that could cause this one to be hidden.
2118       * @return array
2119       */
2120      public function get_dependent_on() {
2121          return $this->dependenton;
2122      }
2123  
2124      /**
2125       * Whether this setting uses a custom form control.
2126       * This function is especially useful to decide if we should render a label element for this setting or not.
2127       *
2128       * @return bool
2129       */
2130      public function has_custom_form_control(): bool {
2131          return $this->customcontrol;
2132      }
2133  }
2134  
2135  /**
2136   * An additional option that can be applied to an admin setting.
2137   * The currently supported options are 'ADVANCED', 'LOCKED' and 'REQUIRED'.
2138   *
2139   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2140   */
2141  class admin_setting_flag {
2142      /** @var bool Flag to indicate if this option can be toggled for this setting */
2143      private $enabled = false;
2144      /** @var bool Flag to indicate if this option defaults to true or false */
2145      private $default = false;
2146      /** @var string Short string used to create setting name - e.g. 'adv' */
2147      private $shortname = '';
2148      /** @var string String used as the label for this flag */
2149      private $displayname = '';
2150      /** @const Checkbox for this flag is displayed in admin page */
2151      const ENABLED = true;
2152      /** @const Checkbox for this flag is not displayed in admin page */
2153      const DISABLED = false;
2154  
2155      /**
2156       * Constructor
2157       *
2158       * @param bool $enabled Can this option can be toggled.
2159       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2160       * @param bool $default The default checked state for this setting option.
2161       * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
2162       * @param string $displayname The displayname of this flag. Used as a label for the flag.
2163       */
2164      public function __construct($enabled, $default, $shortname, $displayname) {
2165          $this->shortname = $shortname;
2166          $this->displayname = $displayname;
2167          $this->set_options($enabled, $default);
2168      }
2169  
2170      /**
2171       * Update the values of this setting options class
2172       *
2173       * @param bool $enabled Can this option can be toggled.
2174       *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2175       * @param bool $default The default checked state for this setting option.
2176       */
2177      public function set_options($enabled, $default) {
2178          $this->enabled = $enabled;
2179          $this->default = $default;
2180      }
2181  
2182      /**
2183       * Should this option appear in the interface and be toggleable?
2184       *
2185       * @return bool Is it enabled?
2186       */
2187      public function is_enabled() {
2188          return $this->enabled;
2189      }
2190  
2191      /**
2192       * Should this option be checked by default?
2193       *
2194       * @return bool Is it on by default?
2195       */
2196      public function get_default() {
2197          return $this->default;
2198      }
2199  
2200      /**
2201       * Return the short name for this flag. e.g. 'adv' or 'locked'
2202       *
2203       * @return string
2204       */
2205      public function get_shortname() {
2206          return $this->shortname;
2207      }
2208  
2209      /**
2210       * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
2211       *
2212       * @return string
2213       */
2214      public function get_displayname() {
2215          return $this->displayname;
2216      }
2217  
2218      /**
2219       * Save the submitted data for this flag - or set it to the default if $data is null.
2220       *
2221       * @param admin_setting $setting - The admin setting for this flag
2222       * @param array $data - The data submitted from the form or null to set the default value for new installs.
2223       * @return bool
2224       */
2225      public function write_setting_flag(admin_setting $setting, $data) {
2226          $result = true;
2227          if ($this->is_enabled()) {
2228              if (!isset($data)) {
2229                  $value = $this->get_default();
2230              } else {
2231                  $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
2232              }
2233              $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
2234          }
2235  
2236          return $result;
2237  
2238      }
2239  
2240      /**
2241       * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
2242       *
2243       * @param admin_setting $setting - The admin setting for this flag
2244       * @return string - The html for the checkbox.
2245       */
2246      public function output_setting_flag(admin_setting $setting) {
2247          global $OUTPUT;
2248  
2249          $value = $setting->get_setting_flag_value($this);
2250  
2251          $context = new stdClass();
2252          $context->id = $setting->get_id() . '_' . $this->get_shortname();
2253          $context->name = $setting->get_full_name() .  '_' . $this->get_shortname();
2254          $context->value = 1;
2255          $context->checked = $value ? true : false;
2256          $context->label = $this->get_displayname();
2257  
2258          return $OUTPUT->render_from_template('core_admin/setting_flag', $context);
2259      }
2260  }
2261  
2262  
2263  /**
2264   * No setting - just heading and text.
2265   *
2266   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2267   */
2268  class admin_setting_heading extends admin_setting {
2269  
2270      /**
2271       * not a setting, just text
2272       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2273       * @param string $heading heading
2274       * @param string $information text in box
2275       */
2276      public function __construct($name, $heading, $information) {
2277          $this->nosave = true;
2278          parent::__construct($name, $heading, $information, '');
2279      }
2280  
2281      /**
2282       * Always returns true
2283       * @return bool Always returns true
2284       */
2285      public function get_setting() {
2286          return true;
2287      }
2288  
2289      /**
2290       * Always returns true
2291       * @return bool Always returns true
2292       */
2293      public function get_defaultsetting() {
2294          return true;
2295      }
2296  
2297      /**
2298       * Never write settings
2299       * @return string Always returns an empty string
2300       */
2301      public function write_setting($data) {
2302      // do not write any setting
2303          return '';
2304      }
2305  
2306      /**
2307       * Returns an HTML string
2308       * @return string Returns an HTML string
2309       */
2310      public function output_html($data, $query='') {
2311          global $OUTPUT;
2312          $context = new stdClass();
2313          $context->title = $this->visiblename;
2314          $context->description = $this->description;
2315          $context->descriptionformatted = highlight($query, markdown_to_html($this->description));
2316          return $OUTPUT->render_from_template('core_admin/setting_heading', $context);
2317      }
2318  }
2319  
2320  /**
2321   * No setting - just name and description in same row.
2322   *
2323   * @copyright 2018 onwards Amaia Anabitarte
2324   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2325   */
2326  class admin_setting_description extends admin_setting {
2327  
2328      /**
2329       * Not a setting, just text
2330       *
2331       * @param string $name
2332       * @param string $visiblename
2333       * @param string $description
2334       */
2335      public function __construct($name, $visiblename, $description) {
2336          $this->nosave = true;
2337          parent::__construct($name, $visiblename, $description, '');
2338      }
2339  
2340      /**
2341       * Always returns true
2342       *
2343       * @return bool Always returns true
2344       */
2345      public function get_setting() {
2346          return true;
2347      }
2348  
2349      /**
2350       * Always returns true
2351       *
2352       * @return bool Always returns true
2353       */
2354      public function get_defaultsetting() {
2355          return true;
2356      }
2357  
2358      /**
2359       * Never write settings
2360       *
2361       * @param mixed $data Gets converted to str for comparison against yes value
2362       * @return string Always returns an empty string
2363       */
2364      public function write_setting($data) {
2365          // Do not write any setting.
2366          return '';
2367      }
2368  
2369      /**
2370       * Returns an HTML string
2371       *
2372       * @param string $data
2373       * @param string $query
2374       * @return string Returns an HTML string
2375       */
2376      public function output_html($data, $query='') {
2377          global $OUTPUT;
2378  
2379          $context = new stdClass();
2380          $context->title = $this->visiblename;
2381          $context->description = $this->description;
2382  
2383          return $OUTPUT->render_from_template('core_admin/setting_description', $context);
2384      }
2385  }
2386  
2387  
2388  
2389  /**
2390   * The most flexible setting, the user enters text.
2391   *
2392   * This type of field should be used for config settings which are using
2393   * English words and are not localised (passwords, database name, list of values, ...).
2394   *
2395   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2396   */
2397  class admin_setting_configtext extends admin_setting {
2398  
2399      /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
2400      public $paramtype;
2401      /** @var int default field size */
2402      public $size;
2403  
2404      /**
2405       * Config text constructor
2406       *
2407       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2408       * @param string $visiblename localised
2409       * @param string $description long localised info
2410       * @param string $defaultsetting
2411       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2412       * @param int $size default field size
2413       */
2414      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2415          $this->paramtype = $paramtype;
2416          if (!is_null($size)) {
2417              $this->size  = $size;
2418          } else {
2419              $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
2420          }
2421          parent::__construct($name, $visiblename, $description, $defaultsetting);
2422      }
2423  
2424      /**
2425       * Get whether this should be displayed in LTR mode.
2426       *
2427       * Try to guess from the PARAM type unless specifically set.
2428       */
2429      public function get_force_ltr() {
2430          $forceltr = parent::get_force_ltr();
2431          if ($forceltr === null) {
2432              return !is_rtl_compatible($this->paramtype);
2433          }
2434          return $forceltr;
2435      }
2436  
2437      /**
2438       * Return the setting
2439       *
2440       * @return mixed returns config if successful else null
2441       */
2442      public function get_setting() {
2443          return $this->config_read($this->name);
2444      }
2445  
2446      public function write_setting($data) {
2447          if ($this->paramtype === PARAM_INT and $data === '') {
2448          // do not complain if '' used instead of 0
2449              $data = 0;
2450          }
2451          // $data is a string
2452          $validated = $this->validate($data);
2453          if ($validated !== true) {
2454              return $validated;
2455          }
2456          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2457      }
2458  
2459      /**
2460       * Validate data before storage
2461       * @param string data
2462       * @return mixed true if ok string if error found
2463       */
2464      public function validate($data) {
2465          // allow paramtype to be a custom regex if it is the form of /pattern/
2466          if (preg_match('#^/.*/$#', $this->paramtype)) {
2467              if (preg_match($this->paramtype, $data)) {
2468                  return true;
2469              } else {
2470                  return get_string('validateerror', 'admin');
2471              }
2472  
2473          } else if ($this->paramtype === PARAM_RAW) {
2474              return true;
2475  
2476          } else {
2477              $cleaned = clean_param($data, $this->paramtype);
2478              if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
2479                  return true;
2480              } else {
2481                  return get_string('validateerror', 'admin');
2482              }
2483          }
2484      }
2485  
2486      /**
2487       * Return an XHTML string for the setting
2488       * @return string Returns an XHTML string
2489       */
2490      public function output_html($data, $query='') {
2491          global $OUTPUT;
2492  
2493          $default = $this->get_defaultsetting();
2494          $context = (object) [
2495              'size' => $this->size,
2496              'id' => $this->get_id(),
2497              'name' => $this->get_full_name(),
2498              'value' => $data,
2499              'forceltr' => $this->get_force_ltr(),
2500              'readonly' => $this->is_readonly(),
2501          ];
2502          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
2503  
2504          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2505      }
2506  }
2507  
2508  /**
2509   * Text input with a maximum length constraint.
2510   *
2511   * @copyright 2015 onwards Ankit Agarwal
2512   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2513   */
2514  class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
2515  
2516      /** @var int maximum number of chars allowed. */
2517      protected $maxlength;
2518  
2519      /**
2520       * Config text constructor
2521       *
2522       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2523       *                     or 'myplugin/mysetting' for ones in config_plugins.
2524       * @param string $visiblename localised
2525       * @param string $description long localised info
2526       * @param string $defaultsetting
2527       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2528       * @param int $size default field size
2529       * @param mixed $maxlength int maxlength allowed, 0 for infinite.
2530       */
2531      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW,
2532                                  $size=null, $maxlength = 0) {
2533          $this->maxlength = $maxlength;
2534          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
2535      }
2536  
2537      /**
2538       * Validate data before storage
2539       *
2540       * @param string $data data
2541       * @return mixed true if ok string if error found
2542       */
2543      public function validate($data) {
2544          $parentvalidation = parent::validate($data);
2545          if ($parentvalidation === true) {
2546              if ($this->maxlength > 0) {
2547                  // Max length check.
2548                  $length = core_text::strlen($data);
2549                  if ($length > $this->maxlength) {
2550                      return get_string('maximumchars', 'moodle',  $this->maxlength);
2551                  }
2552                  return true;
2553              } else {
2554                  return true; // No max length check needed.
2555              }
2556          } else {
2557              return $parentvalidation;
2558          }
2559      }
2560  }
2561  
2562  /**
2563   * General text area without html editor.
2564   *
2565   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2566   */
2567  class admin_setting_configtextarea extends admin_setting_configtext {
2568      private $rows;
2569      private $cols;
2570  
2571      /**
2572       * @param string $name
2573       * @param string $visiblename
2574       * @param string $description
2575       * @param mixed $defaultsetting string or array
2576       * @param mixed $paramtype
2577       * @param string $cols The number of columns to make the editor
2578       * @param string $rows The number of rows to make the editor
2579       */
2580      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2581          $this->rows = $rows;
2582          $this->cols = $cols;
2583          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
2584      }
2585  
2586      /**
2587       * Returns an XHTML string for the editor
2588       *
2589       * @param string $data
2590       * @param string $query
2591       * @return string XHTML string for the editor
2592       */
2593      public function output_html($data, $query='') {
2594          global $OUTPUT;
2595  
2596          $default = $this->get_defaultsetting();
2597          $defaultinfo = $default;
2598          if (!is_null($default) and $default !== '') {
2599              $defaultinfo = "\n".$default;
2600          }
2601  
2602          $context = (object) [
2603              'cols' => $this->cols,
2604              'rows' => $this->rows,
2605              'id' => $this->get_id(),
2606              'name' => $this->get_full_name(),
2607              'value' => $data,
2608              'forceltr' => $this->get_force_ltr(),
2609              'readonly' => $this->is_readonly(),
2610          ];
2611          $element = $OUTPUT->render_from_template('core_admin/setting_configtextarea', $context);
2612  
2613          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
2614      }
2615  }
2616  
2617  /**
2618   * General text area with html editor.
2619   */
2620  class admin_setting_confightmleditor extends admin_setting_configtextarea {
2621  
2622      /**
2623       * @param string $name
2624       * @param string $visiblename
2625       * @param string $description
2626       * @param mixed $defaultsetting string or array
2627       * @param mixed $paramtype
2628       */
2629      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2630          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
2631          $this->set_force_ltr(false);
2632          editors_head_setup();
2633      }
2634  
2635      /**
2636       * Returns an XHTML string for the editor
2637       *
2638       * @param string $data
2639       * @param string $query
2640       * @return string XHTML string for the editor
2641       */
2642      public function output_html($data, $query='') {
2643          $editor = editors_get_preferred_editor(FORMAT_HTML);
2644          $editor->set_text($data);
2645          $editor->use_editor($this->get_id(), array('noclean'=>true));
2646          return parent::output_html($data, $query);
2647      }
2648  
2649      /**
2650       * Checks if data has empty html.
2651       *
2652       * @param string $data
2653       * @return string Empty when no errors.
2654       */
2655      public function write_setting($data) {
2656          if (trim(html_to_text($data)) === '') {
2657              $data = '';
2658          }
2659          return parent::write_setting($data);
2660      }
2661  }
2662  
2663  
2664  /**
2665   * Password field, allows unmasking of password
2666   *
2667   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2668   */
2669  class admin_setting_configpasswordunmask extends admin_setting_configtext {
2670  
2671      /**
2672       * Constructor
2673       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2674       * @param string $visiblename localised
2675       * @param string $description long localised info
2676       * @param string $defaultsetting default password
2677       */
2678      public function __construct($name, $visiblename, $description, $defaultsetting) {
2679          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2680      }
2681  
2682      /**
2683       * Log config changes if necessary.
2684       * @param string $name
2685       * @param string $oldvalue
2686       * @param string $value
2687       */
2688      protected function add_to_config_log($name, $oldvalue, $value) {
2689          if ($value !== '') {
2690              $value = '********';
2691          }
2692          if ($oldvalue !== '' and $oldvalue !== null) {
2693              $oldvalue = '********';
2694          }
2695          parent::add_to_config_log($name, $oldvalue, $value);
2696      }
2697  
2698      /**
2699       * Returns HTML for the field.
2700       *
2701       * @param   string  $data       Value for the field
2702       * @param   string  $query      Passed as final argument for format_admin_setting
2703       * @return  string              Rendered HTML
2704       */
2705      public function output_html($data, $query='') {
2706          global $OUTPUT;
2707  
2708          $context = (object) [
2709              'id' => $this->get_id(),
2710              'name' => $this->get_full_name(),
2711              'size' => $this->size,
2712              'value' => $this->is_readonly() ? null : $data,
2713              'forceltr' => $this->get_force_ltr(),
2714              'readonly' => $this->is_readonly(),
2715          ];
2716          $element = $OUTPUT->render_from_template('core_admin/setting_configpasswordunmask', $context);
2717          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', null, $query);
2718      }
2719  }
2720  
2721  /**
2722   * Password field, allows unmasking of password, with an advanced checkbox that controls an additional $name.'_adv' setting.
2723   *
2724   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2725   * @copyright 2018 Paul Holden (pholden@greenhead.ac.uk)
2726   */
2727  class admin_setting_configpasswordunmask_with_advanced extends admin_setting_configpasswordunmask {
2728  
2729      /**
2730       * Constructor
2731       *
2732       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2733       * @param string $visiblename localised
2734       * @param string $description long localised info
2735       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
2736       */
2737      public function __construct($name, $visiblename, $description, $defaultsetting) {
2738          parent::__construct($name, $visiblename, $description, $defaultsetting['value']);
2739          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
2740      }
2741  }
2742  
2743  /**
2744   * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
2745   * Note: Only advanced makes sense right now - locked does not.
2746   *
2747   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2748   */
2749  class admin_setting_configempty extends admin_setting_configtext {
2750  
2751      /**
2752       * @param string $name
2753       * @param string $visiblename
2754       * @param string $description
2755       */
2756      public function __construct($name, $visiblename, $description) {
2757          parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
2758      }
2759  
2760      /**
2761       * Returns an XHTML string for the hidden field
2762       *
2763       * @param string $data
2764       * @param string $query
2765       * @return string XHTML string for the editor
2766       */
2767      public function output_html($data, $query='') {
2768          global $OUTPUT;
2769  
2770          $context = (object) [
2771              'id' => $this->get_id(),
2772              'name' => $this->get_full_name()
2773          ];
2774          $element = $OUTPUT->render_from_template('core_admin/setting_configempty', $context);
2775  
2776          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', get_string('none'), $query);
2777      }
2778  }
2779  
2780  
2781  /**
2782   * Path to directory
2783   *
2784   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2785   */
2786  class admin_setting_configfile extends admin_setting_configtext {
2787      /**
2788       * Constructor
2789       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2790       * @param string $visiblename localised
2791       * @param string $description long localised info
2792       * @param string $defaultdirectory default directory location
2793       */
2794      public function __construct($name, $visiblename, $description, $defaultdirectory) {
2795          parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2796      }
2797  
2798      /**
2799       * Returns XHTML for the field
2800       *
2801       * Returns XHTML for the field and also checks whether the file
2802       * specified in $data exists using file_exists()
2803       *
2804       * @param string $data File name and path to use in value attr
2805       * @param string $query
2806       * @return string XHTML field
2807       */
2808      public function output_html($data, $query='') {
2809          global $CFG, $OUTPUT;
2810  
2811          $default = $this->get_defaultsetting();
2812          $context = (object) [
2813              'id' => $this->get_id(),
2814              'name' => $this->get_full_name(),
2815              'size' => $this->size,
2816              'value' => $data,
2817              'showvalidity' => !empty($data),
2818              'valid' => $data && file_exists($data),
2819              'readonly' => !empty($CFG->preventexecpath) || $this->is_readonly(),
2820              'forceltr' => $this->get_force_ltr(),
2821          ];
2822  
2823          if ($context->readonly) {
2824              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2825          }
2826  
2827          $element = $OUTPUT->render_from_template('core_admin/setting_configfile', $context);
2828  
2829          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2830      }
2831  
2832      /**
2833       * Checks if execpatch has been disabled in config.php
2834       */
2835      public function write_setting($data) {
2836          global $CFG;
2837          if (!empty($CFG->preventexecpath)) {
2838              if ($this->get_setting() === null) {
2839                  // Use default during installation.
2840                  $data = $this->get_defaultsetting();
2841                  if ($data === null) {
2842                      $data = '';
2843                  }
2844              } else {
2845                  return '';
2846              }
2847          }
2848          return parent::write_setting($data);
2849      }
2850  
2851  }
2852  
2853  
2854  /**
2855   * Path to executable file
2856   *
2857   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2858   */
2859  class admin_setting_configexecutable extends admin_setting_configfile {
2860  
2861      /**
2862       * Returns an XHTML field
2863       *
2864       * @param string $data This is the value for the field
2865       * @param string $query
2866       * @return string XHTML field
2867       */
2868      public function output_html($data, $query='') {
2869          global $CFG, $OUTPUT;
2870          $default = $this->get_defaultsetting();
2871          require_once("$CFG->libdir/filelib.php");
2872  
2873          $context = (object) [
2874              'id' => $this->get_id(),
2875              'name' => $this->get_full_name(),
2876              'size' => $this->size,
2877              'value' => $data,
2878              'showvalidity' => !empty($data),
2879              'valid' => $data && file_exists($data) && !is_dir($data) && file_is_executable($data),
2880              'readonly' => !empty($CFG->preventexecpath),
2881              'forceltr' => $this->get_force_ltr()
2882          ];
2883  
2884          if (!empty($CFG->preventexecpath)) {
2885              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2886          }
2887  
2888          $element = $OUTPUT->render_from_template('core_admin/setting_configexecutable', $context);
2889  
2890          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2891      }
2892  }
2893  
2894  
2895  /**
2896   * Path to directory
2897   *
2898   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2899   */
2900  class admin_setting_configdirectory extends admin_setting_configfile {
2901  
2902      /**
2903       * Returns an XHTML field
2904       *
2905       * @param string $data This is the value for the field
2906       * @param string $query
2907       * @return string XHTML
2908       */
2909      public function output_html($data, $query='') {
2910          global $CFG, $OUTPUT;
2911          $default = $this->get_defaultsetting();
2912  
2913          $context = (object) [
2914              'id' => $this->get_id(),
2915              'name' => $this->get_full_name(),
2916              'size' => $this->size,
2917              'value' => $data,
2918              'showvalidity' => !empty($data),
2919              'valid' => $data && file_exists($data) && is_dir($data),
2920              'readonly' => !empty($CFG->preventexecpath),
2921              'forceltr' => $this->get_force_ltr()
2922          ];
2923  
2924          if (!empty($CFG->preventexecpath)) {
2925              $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2926          }
2927  
2928          $element = $OUTPUT->render_from_template('core_admin/setting_configdirectory', $context);
2929  
2930          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2931      }
2932  }
2933  
2934  
2935  /**
2936   * Checkbox
2937   *
2938   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2939   */
2940  class admin_setting_configcheckbox extends admin_setting {
2941      /** @var string Value used when checked */
2942      public $yes;
2943      /** @var string Value used when not checked */
2944      public $no;
2945  
2946      /**
2947       * Constructor
2948       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2949       * @param string $visiblename localised
2950       * @param string $description long localised info
2951       * @param string $defaultsetting
2952       * @param string $yes value used when checked
2953       * @param string $no value used when not checked
2954       */
2955      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2956          parent::__construct($name, $visiblename, $description, $defaultsetting);
2957          $this->yes = (string)$yes;
2958          $this->no  = (string)$no;
2959      }
2960  
2961      /**
2962       * Retrieves the current setting using the objects name
2963       *
2964       * @return string
2965       */
2966      public function get_setting() {
2967          return $this->config_read($this->name);
2968      }
2969  
2970      /**
2971       * Sets the value for the setting
2972       *
2973       * Sets the value for the setting to either the yes or no values
2974       * of the object by comparing $data to yes
2975       *
2976       * @param mixed $data Gets converted to str for comparison against yes value
2977       * @return string empty string or error
2978       */
2979      public function write_setting($data) {
2980          if ((string)$data === $this->yes) { // convert to strings before comparison
2981              $data = $this->yes;
2982          } else {
2983              $data = $this->no;
2984          }
2985          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2986      }
2987  
2988      /**
2989       * Returns an XHTML checkbox field
2990       *
2991       * @param string $data If $data matches yes then checkbox is checked
2992       * @param string $query
2993       * @return string XHTML field
2994       */
2995      public function output_html($data, $query='') {
2996          global $OUTPUT;
2997  
2998          $context = (object) [
2999              'id' => $this->get_id(),
3000              'name' => $this->get_full_name(),
3001              'no' => $this->no,
3002              'value' => $this->yes,
3003              'checked' => (string) $data === $this->yes,
3004              'readonly' => $this->is_readonly(),
3005          ];
3006  
3007          $default = $this->get_defaultsetting();
3008          if (!is_null($default)) {
3009              if ((string)$default === $this->yes) {
3010                  $defaultinfo = get_string('checkboxyes', 'admin');
3011              } else {
3012                  $defaultinfo = get_string('checkboxno', 'admin');
3013              }
3014          } else {
3015              $defaultinfo = NULL;
3016          }
3017  
3018          $element = $OUTPUT->render_from_template('core_admin/setting_configcheckbox', $context);
3019  
3020          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3021      }
3022  }
3023  
3024  
3025  /**
3026   * Multiple checkboxes, each represents different value, stored in csv format
3027   *
3028   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3029   */
3030  class admin_setting_configmulticheckbox extends admin_setting {
3031      /** @var array Array of choices value=>label */
3032      public $choices;
3033  
3034      /**
3035       * Constructor: uses parent::__construct
3036       *
3037       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3038       * @param string $visiblename localised
3039       * @param string $description long localised info
3040       * @param array $defaultsetting array of selected
3041       * @param array $choices array of $value=>$label for each checkbox
3042       */
3043      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3044          $this->choices = $choices;
3045          parent::__construct($name, $visiblename, $description, $defaultsetting);
3046      }
3047  
3048      /**
3049       * This public function may be used in ancestors for lazy loading of choices
3050       *
3051       * @todo Check if this function is still required content commented out only returns true
3052       * @return bool true if loaded, false if error
3053       */
3054      public function load_choices() {
3055          /*
3056          if (is_array($this->choices)) {
3057              return true;
3058          }
3059          .... load choices here
3060          */
3061          return true;
3062      }
3063  
3064      /**
3065       * Is setting related to query text - used when searching
3066       *
3067       * @param string $query
3068       * @return bool true on related, false on not or failure
3069       */
3070      public function is_related($query) {
3071          if (!$this->load_choices() or empty($this->choices)) {
3072              return false;
3073          }
3074          if (parent::is_related($query)) {
3075              return true;
3076          }
3077  
3078          foreach ($this->choices as $desc) {
3079              if (strpos(core_text::strtolower($desc), $query) !== false) {
3080                  return true;
3081              }
3082          }
3083          return false;
3084      }
3085  
3086      /**
3087       * Returns the current setting if it is set
3088       *
3089       * @return mixed null if null, else an array
3090       */
3091      public function get_setting() {
3092          $result = $this->config_read($this->name);
3093  
3094          if (is_null($result)) {
3095              return NULL;
3096          }
3097          if ($result === '') {
3098              return array();
3099          }
3100          $enabled = explode(',', $result);
3101          $setting = array();
3102          foreach ($enabled as $option) {
3103              $setting[$option] = 1;
3104          }
3105          return $setting;
3106      }
3107  
3108      /**
3109       * Saves the setting(s) provided in $data
3110       *
3111       * @param array $data An array of data, if not array returns empty str
3112       * @return mixed empty string on useless data or bool true=success, false=failed
3113       */
3114      public function write_setting($data) {
3115          if (!is_array($data)) {
3116              return ''; // ignore it
3117          }
3118          if (!$this->load_choices() or empty($this->choices)) {
3119              return '';
3120          }
3121          unset($data['xxxxx']);
3122          $result = array();
3123          foreach ($data as $key => $value) {
3124              if ($value and array_key_exists($key, $this->choices)) {
3125                  $result[] = $key;
3126              }
3127          }
3128          return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
3129      }
3130  
3131      /**
3132       * Returns XHTML field(s) as required by choices
3133       *
3134       * Relies on data being an array should data ever be another valid vartype with
3135       * acceptable value this may cause a warning/error
3136       * if (!is_array($data)) would fix the problem
3137       *
3138       * @todo Add vartype handling to ensure $data is an array
3139       *
3140       * @param array $data An array of checked values
3141       * @param string $query
3142       * @return string XHTML field
3143       */
3144      public function output_html($data, $query='') {
3145          global $OUTPUT;
3146  
3147          if (!$this->load_choices() or empty($this->choices)) {
3148              return '';
3149          }
3150  
3151          $default = $this->get_defaultsetting();
3152          if (is_null($default)) {
3153              $default = array();
3154          }
3155          if (is_null($data)) {
3156              $data = array();
3157          }
3158  
3159          $context = (object) [
3160              'id' => $this->get_id(),
3161              'name' => $this->get_full_name(),
3162          ];
3163  
3164          $options = array();
3165          $defaults = array();
3166          foreach ($this->choices as $key => $description) {
3167              if (!empty($default[$key])) {
3168                  $defaults[] = $description;
3169              }
3170  
3171              $options[] = [
3172                  'key' => $key,
3173                  'checked' => !empty($data[$key]),
3174                  'label' => highlightfast($query, $description)
3175              ];
3176          }
3177  
3178          if (is_null($default)) {
3179              $defaultinfo = null;
3180          } else if (!empty($defaults)) {
3181              $defaultinfo = implode(', ', $defaults);
3182          } else {
3183              $defaultinfo = get_string('none');
3184          }
3185  
3186          $context->options = $options;
3187          $context->hasoptions = !empty($options);
3188  
3189          $element = $OUTPUT->render_from_template('core_admin/setting_configmulticheckbox', $context);
3190  
3191          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', $defaultinfo, $query);
3192  
3193      }
3194  }
3195  
3196  
3197  /**
3198   * Multiple checkboxes 2, value stored as string 00101011
3199   *
3200   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3201   */
3202  class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
3203  
3204      /**
3205       * Returns the setting if set
3206       *
3207       * @return mixed null if not set, else an array of set settings
3208       */
3209      public function get_setting() {
3210          $result = $this->config_read($this->name);
3211          if (is_null($result)) {
3212              return NULL;
3213          }
3214          if (!$this->load_choices()) {
3215              return NULL;
3216          }
3217          $result = str_pad($result, count($this->choices), '0');
3218          $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
3219          $setting = array();
3220          foreach ($this->choices as $key=>$unused) {
3221              $value = array_shift($result);
3222              if ($value) {
3223                  $setting[$key] = 1;
3224              }
3225          }
3226          return $setting;
3227      }
3228  
3229      /**
3230       * Save setting(s) provided in $data param
3231       *
3232       * @param array $data An array of settings to save
3233       * @return mixed empty string for bad data or bool true=>success, false=>error
3234       */
3235      public function write_setting($data) {
3236          if (!is_array($data)) {
3237              return ''; // ignore it
3238          }
3239          if (!$this->load_choices() or empty($this->choices)) {
3240              return '';
3241          }
3242          $result = '';
3243          foreach ($this->choices as $key=>$unused) {
3244              if (!empty($data[$key])) {
3245                  $result .= '1';
3246              } else {
3247                  $result .= '0';
3248              }
3249          }
3250          return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
3251      }
3252  }
3253  
3254  
3255  /**
3256   * Select one value from list
3257   *
3258   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3259   */
3260  class admin_setting_configselect extends admin_setting {
3261      /** @var array Array of choices value=>label */
3262      public $choices;
3263      /** @var array Array of choices grouped using optgroups */
3264      public $optgroups;
3265      /** @var callable|null Loader function for choices */
3266      protected $choiceloader = null;
3267      /** @var callable|null Validation function */
3268      protected $validatefunction = null;
3269  
3270      /**
3271       * Constructor.
3272       *
3273       * If you want to lazy-load the choices, pass a callback function that returns a choice
3274       * array for the $choices parameter.
3275       *
3276       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3277       * @param string $visiblename localised
3278       * @param string $description long localised info
3279       * @param string|int $defaultsetting
3280       * @param array|callable|null $choices array of $value=>$label for each selection, or callback
3281       */
3282      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3283          // Look for optgroup and single options.
3284          if (is_array($choices)) {
3285              $this->choices = [];
3286              foreach ($choices as $key => $val) {
3287                  if (is_array($val)) {
3288                      $this->optgroups[$key] = $val;
3289                      $this->choices = array_merge($this->choices, $val);
3290                  } else {
3291                      $this->choices[$key] = $val;
3292                  }
3293              }
3294          }
3295          if (is_callable($choices)) {
3296              $this->choiceloader = $choices;
3297          }
3298  
3299          parent::__construct($name, $visiblename, $description, $defaultsetting);
3300      }
3301  
3302      /**
3303       * Sets a validate function.
3304       *
3305       * The callback will be passed one parameter, the new setting value, and should return either
3306       * an empty string '' if the value is OK, or an error message if not.
3307       *
3308       * @param callable|null $validatefunction Validate function or null to clear
3309       * @since Moodle 3.10
3310       */
3311      public function set_validate_function(?callable $validatefunction = null) {
3312          $this->validatefunction = $validatefunction;
3313      }
3314  
3315      /**
3316       * This function may be used in ancestors for lazy loading of choices
3317       *
3318       * Override this method if loading of choices is expensive, such
3319       * as when it requires multiple db requests.
3320       *
3321       * @return bool true if loaded, false if error
3322       */
3323      public function load_choices() {
3324          if ($this->choiceloader) {
3325              if (!is_array($this->choices)) {
3326                  $this->choices = call_user_func($this->choiceloader);
3327              }
3328              return true;
3329          }
3330          return true;
3331      }
3332  
3333      /**
3334       * Check if this is $query is related to a choice
3335       *
3336       * @param string $query
3337       * @return bool true if related, false if not
3338       */
3339      public function is_related($query) {
3340          if (parent::is_related($query)) {
3341              return true;
3342          }
3343          if (!$this->load_choices()) {
3344              return false;
3345          }
3346          foreach ($this->choices as $key=>$value) {
3347              if (strpos(core_text::strtolower($key), $query) !== false) {
3348                  return true;
3349              }
3350              if (strpos(core_text::strtolower($value), $query) !== false) {
3351                  return true;
3352              }
3353          }
3354          return false;
3355      }
3356  
3357      /**
3358       * Return the setting
3359       *
3360       * @return mixed returns config if successful else null
3361       */
3362      public function get_setting() {
3363          return $this->config_read($this->name);
3364      }
3365  
3366      /**
3367       * Save a setting
3368       *
3369       * @param string $data
3370       * @return string empty of error string
3371       */
3372      public function write_setting($data) {
3373          if (!$this->load_choices() or empty($this->choices)) {
3374              return '';
3375          }
3376          if (!array_key_exists($data, $this->choices)) {
3377              return ''; // ignore it
3378          }
3379  
3380          // Validate the new setting.
3381          $error = $this->validate_setting($data);
3382          if ($error) {
3383              return $error;
3384          }
3385  
3386          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3387      }
3388  
3389      /**
3390       * Validate the setting. This uses the callback function if provided; subclasses could override
3391       * to carry out validation directly in the class.
3392       *
3393       * @param string $data New value being set
3394       * @return string Empty string if valid, or error message text
3395       * @since Moodle 3.10
3396       */
3397      protected function validate_setting(string $data): string {
3398          // If validation function is specified, call it now.
3399          if ($this->validatefunction) {
3400              return call_user_func($this->validatefunction, $data);
3401          } else {
3402              return '';
3403          }
3404      }
3405  
3406      /**
3407       * Returns XHTML select field
3408       *
3409       * Ensure the options are loaded, and generate the XHTML for the select
3410       * element and any warning message. Separating this out from output_html
3411       * makes it easier to subclass this class.
3412       *
3413       * @param string $data the option to show as selected.
3414       * @param string $current the currently selected option in the database, null if none.
3415       * @param string $default the default selected option.
3416       * @return array the HTML for the select element, and a warning message.
3417       * @deprecated since Moodle 3.2
3418       */
3419      public function output_select_html($data, $current, $default, $extraname = '') {
3420          debugging('The method admin_setting_configselect::output_select_html is depreacted, do not use any more.', DEBUG_DEVELOPER);
3421      }
3422  
3423      /**
3424       * Returns XHTML select field and wrapping div(s)
3425       *
3426       * @see output_select_html()
3427       *
3428       * @param string $data the option to show as selected
3429       * @param string $query
3430       * @return string XHTML field and wrapping div
3431       */
3432      public function output_html($data, $query='') {
3433          global $OUTPUT;
3434  
3435          $default = $this->get_defaultsetting();
3436          $current = $this->get_setting();
3437  
3438          if (!$this->load_choices() || empty($this->choices)) {
3439              return '';
3440          }
3441  
3442          $context = (object) [
3443              'id' => $this->get_id(),
3444              'name' => $this->get_full_name(),
3445          ];
3446  
3447          if (!is_null($default) && array_key_exists($default, $this->choices)) {
3448              $defaultinfo = $this->choices[$default];
3449          } else {
3450              $defaultinfo = NULL;
3451          }
3452  
3453          // Warnings.
3454          $warning = '';
3455          if ($current === null) {
3456              // First run.
3457          } else if (empty($current) && (array_key_exists('', $this->choices) || array_key_exists(0, $this->choices))) {
3458              // No warning.
3459          } else if (!array_key_exists($current, $this->choices)) {
3460              $warning = get_string('warningcurrentsetting', 'admin', $current);
3461              if (!is_null($default) && $data == $current) {
3462                  $data = $default; // Use default instead of first value when showing the form.
3463              }
3464          }
3465  
3466          $options = [];
3467          $template = 'core_admin/setting_configselect';
3468  
3469          if (!empty($this->optgroups)) {
3470              $optgroups = [];
3471              foreach ($this->optgroups as $label => $choices) {
3472                  $optgroup = array('label' => $label, 'options' => []);
3473                  foreach ($choices as $value => $name) {
3474                      $optgroup['options'][] = [
3475                          'value' => $value,
3476                          'name' => $name,
3477                          'selected' => (string) $value == $data
3478                      ];
3479                      unset($this->choices[$value]);
3480                  }
3481                  $optgroups[] = $optgroup;
3482              }
3483              $context->options = $options;
3484              $context->optgroups = $optgroups;
3485              $template = 'core_admin/setting_configselect_optgroup';
3486          }
3487  
3488          foreach ($this->choices as $value => $name) {
3489              $options[] = [
3490                  'value' => $value,
3491                  'name' => $name,
3492                  'selected' => (string) $value == $data
3493              ];
3494          }
3495          $context->options = $options;
3496          $context->readonly = $this->is_readonly();
3497  
3498          $element = $OUTPUT->render_from_template($template, $context);
3499  
3500          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, $warning, $defaultinfo, $query);
3501      }
3502  }
3503  
3504  /**
3505   * Select multiple items from list
3506   *
3507   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3508   */
3509  class admin_setting_configmultiselect extends admin_setting_configselect {
3510      /**
3511       * Constructor
3512       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3513       * @param string $visiblename localised
3514       * @param string $description long localised info
3515       * @param array $defaultsetting array of selected items
3516       * @param array $choices array of $value=>$label for each list item
3517       */
3518      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3519          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
3520      }
3521  
3522      /**
3523       * Returns the select setting(s)
3524       *
3525       * @return mixed null or array. Null if no settings else array of setting(s)
3526       */
3527      public function get_setting() {
3528          $result = $this->config_read($this->name);
3529          if (is_null($result)) {
3530              return NULL;
3531          }
3532          if ($result === '') {
3533              return array();
3534          }
3535          return explode(',', $result);
3536      }
3537  
3538      /**
3539       * Saves setting(s) provided through $data
3540       *
3541       * Potential bug in the works should anyone call with this function
3542       * using a vartype that is not an array
3543       *
3544       * @param array $data
3545       */
3546      public function write_setting($data) {
3547          if (!is_array($data)) {
3548              return ''; //ignore it
3549          }
3550          if (!$this->load_choices() or empty($this->choices)) {
3551              return '';
3552          }
3553  
3554          unset($data['xxxxx']);
3555  
3556          $save = array();
3557          foreach ($data as $value) {
3558              if (!array_key_exists($value, $this->choices)) {
3559                  continue; // ignore it
3560              }
3561              $save[] = $value;
3562          }
3563  
3564          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3565      }
3566  
3567      /**
3568       * Is setting related to query text - used when searching
3569       *
3570       * @param string $query
3571       * @return bool true if related, false if not
3572       */
3573      public function is_related($query) {
3574          if (!$this->load_choices() or empty($this->choices)) {
3575              return false;
3576          }
3577          if (parent::is_related($query)) {
3578              return true;
3579          }
3580  
3581          foreach ($this->choices as $desc) {
3582              if (strpos(core_text::strtolower($desc), $query) !== false) {
3583                  return true;
3584              }
3585          }
3586          return false;
3587      }
3588  
3589      /**
3590       * Returns XHTML multi-select field
3591       *
3592       * @todo Add vartype handling to ensure $data is an array
3593       * @param array $data Array of values to select by default
3594       * @param string $query
3595       * @return string XHTML multi-select field
3596       */
3597      public function output_html($data, $query='') {
3598          global $OUTPUT;
3599  
3600          if (!$this->load_choices() or empty($this->choices)) {
3601              return '';
3602          }
3603  
3604          $default = $this->get_defaultsetting();
3605          if (is_null($default)) {
3606              $default = array();
3607          }
3608          if (is_null($data)) {
3609              $data = array();
3610          }
3611  
3612          $context = (object) [
3613              'id' => $this->get_id(),
3614              'name' => $this->get_full_name(),
3615              'size' => min(10, count($this->choices))
3616          ];
3617  
3618          $defaults = [];
3619          $options = [];
3620          $template = 'core_admin/setting_configmultiselect';
3621  
3622          if (!empty($this->optgroups)) {
3623              $optgroups = [];
3624              foreach ($this->optgroups as $label => $choices) {
3625                  $optgroup = array('label' => $label, 'options' => []);
3626                  foreach ($choices as $value => $name) {
3627                      if (in_array($value, $default)) {
3628                          $defaults[] = $name;
3629                      }
3630                      $optgroup['options'][] = [
3631                          'value' => $value,
3632                          'name' => $name,
3633                          'selected' => in_array($value, $data)
3634                      ];
3635                      unset($this->choices[$value]);
3636                  }
3637                  $optgroups[] = $optgroup;
3638              }
3639              $context->optgroups = $optgroups;
3640              $template = 'core_admin/setting_configmultiselect_optgroup';
3641          }
3642  
3643          foreach ($this->choices as $value => $name) {
3644              if (in_array($value, $default)) {
3645                  $defaults[] = $name;
3646              }
3647              $options[] = [
3648                  'value' => $value,
3649                  'name' => $name,
3650                  'selected' => in_array($value, $data)
3651              ];
3652          }
3653          $context->options = $options;
3654          $context->readonly = $this->is_readonly();
3655  
3656          if (is_null($default)) {
3657              $defaultinfo = NULL;
3658          } if (!empty($defaults)) {
3659              $defaultinfo = implode(', ', $defaults);
3660          } else {
3661              $defaultinfo = get_string('none');
3662          }
3663  
3664          $element = $OUTPUT->render_from_template($template, $context);
3665  
3666          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3667      }
3668  }
3669  
3670  /**
3671   * Time selector
3672   *
3673   * This is a liiitle bit messy. we're using two selects, but we're returning
3674   * them as an array named after $name (so we only use $name2 internally for the setting)
3675   *
3676   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3677   */
3678  class admin_setting_configtime extends admin_setting {
3679      /** @var string Used for setting second select (minutes) */
3680      public $name2;
3681  
3682      /**
3683       * Constructor
3684       * @param string $hoursname setting for hours
3685       * @param string $minutesname setting for hours
3686       * @param string $visiblename localised
3687       * @param string $description long localised info
3688       * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3689       */
3690      public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
3691          $this->name2 = $minutesname;
3692          parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
3693      }
3694  
3695      /**
3696       * Get the selected time
3697       *
3698       * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
3699       */
3700      public function get_setting() {
3701          $result1 = $this->config_read($this->name);
3702          $result2 = $this->config_read($this->name2);
3703          if (is_null($result1) or is_null($result2)) {
3704              return NULL;
3705          }
3706  
3707          return array('h' => $result1, 'm' => $result2);
3708      }
3709  
3710      /**
3711       * Store the time (hours and minutes)
3712       *
3713       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3714       * @return bool true if success, false if not
3715       */
3716      public function write_setting($data) {
3717          if (!is_array($data)) {
3718              return '';
3719          }
3720  
3721          $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3722          return ($result ? '' : get_string('errorsetting', 'admin'));
3723      }
3724  
3725      /**
3726       * Returns XHTML time select fields
3727       *
3728       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3729       * @param string $query
3730       * @return string XHTML time select fields and wrapping div(s)
3731       */
3732      public function output_html($data, $query='') {
3733          global $OUTPUT;
3734  
3735          $default = $this->get_defaultsetting();
3736          if (is_array($default)) {
3737              $defaultinfo = $default['h'].':'.$default['m'];
3738          } else {
3739              $defaultinfo = NULL;
3740          }
3741  
3742          $context = (object) [
3743              'id' => $this->get_id(),
3744              'name' => $this->get_full_name(),
3745              'readonly' => $this->is_readonly(),
3746              'hours' => array_map(function($i) use ($data) {
3747                  return [
3748                      'value' => $i,
3749                      'name' => $i,
3750                      'selected' => $i == $data['h']
3751                  ];
3752              }, range(0, 23)),
3753              'minutes' => array_map(function($i) use ($data) {
3754                  return [
3755                      'value' => $i,
3756                      'name' => $i,
3757                      'selected' => $i == $data['m']
3758                  ];
3759              }, range(0, 59, 5))
3760          ];
3761  
3762          $element = $OUTPUT->render_from_template('core_admin/setting_configtime', $context);
3763  
3764          return format_admin_setting($this, $this->visiblename, $element, $this->description,
3765              $this->get_id() . 'h', '', $defaultinfo, $query);
3766      }
3767  
3768  }
3769  
3770  
3771  /**
3772   * Seconds duration setting.
3773   *
3774   * @copyright 2012 Petr Skoda (http://skodak.org)
3775   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3776   */
3777  class admin_setting_configduration extends admin_setting {
3778  
3779      /** @var int default duration unit */
3780      protected $defaultunit;
3781  
3782      /**
3783       * Constructor
3784       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3785       *                     or 'myplugin/mysetting' for ones in config_plugins.
3786       * @param string $visiblename localised name
3787       * @param string $description localised long description
3788       * @param mixed $defaultsetting string or array depending on implementation
3789       * @param int $defaultunit - day, week, etc. (in seconds)
3790       */
3791      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3792          if (is_number($defaultsetting)) {
3793              $defaultsetting = self::parse_seconds($defaultsetting);
3794          }
3795          $units = self::get_units();
3796          if (isset($units[$defaultunit])) {
3797              $this->defaultunit = $defaultunit;
3798          } else {
3799              $this->defaultunit = 86400;
3800          }
3801          parent::__construct($name, $visiblename, $description, $defaultsetting);
3802      }
3803  
3804      /**
3805       * Returns selectable units.
3806       * @static
3807       * @return array
3808       */
3809      protected static function get_units() {
3810          return array(
3811              604800 => get_string('weeks'),
3812              86400 => get_string('days'),
3813              3600 => get_string('hours'),
3814              60 => get_string('minutes'),
3815              1 => get_string('seconds'),
3816          );
3817      }
3818  
3819      /**
3820       * Converts seconds to some more user friendly string.
3821       * @static
3822       * @param int $seconds
3823       * @return string
3824       */
3825      protected static function get_duration_text($seconds) {
3826          if (empty($seconds)) {
3827              return get_string('none');
3828          }
3829          $data = self::parse_seconds($seconds);
3830          switch ($data['u']) {
3831              case (60*60*24*7):
3832                  return get_string('numweeks', '', $data['v']);
3833              case (60*60*24):
3834                  return get_string('numdays', '', $data['v']);
3835              case (60*60):
3836                  return get_string('numhours', '', $data['v']);
3837              case (60):
3838                  return get_string('numminutes', '', $data['v']);
3839              default:
3840                  return get_string('numseconds', '', $data['v']*$data['u']);
3841          }
3842      }
3843  
3844      /**
3845       * Finds suitable units for given duration.
3846       * @static
3847       * @param int $seconds
3848       * @return array
3849       */
3850      protected static function parse_seconds($seconds) {
3851          foreach (self::get_units() as $unit => $unused) {
3852              if ($seconds % $unit === 0) {
3853                  return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
3854              }
3855          }
3856          return array('v'=>(int)$seconds, 'u'=>1);
3857      }
3858  
3859      /**
3860       * Get the selected duration as array.
3861       *
3862       * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
3863       */
3864      public function get_setting() {
3865          $seconds = $this->config_read($this->name);
3866          if (is_null($seconds)) {
3867              return null;
3868          }
3869  
3870          return self::parse_seconds($seconds);
3871      }
3872  
3873      /**
3874       * Store the duration as seconds.
3875       *
3876       * @param array $data Must be form 'h'=>xx, 'm'=>xx
3877       * @return bool true if success, false if not
3878       */
3879      public function write_setting($data) {
3880          if (!is_array($data)) {
3881              return '';
3882          }
3883  
3884          $seconds = (int)($data['v']*$data['u']);
3885          if ($seconds < 0) {
3886              return get_string('errorsetting', 'admin');
3887          }
3888  
3889          $result = $this->config_write($this->name, $seconds);
3890          return ($result ? '' : get_string('errorsetting', 'admin'));
3891      }
3892  
3893      /**
3894       * Returns duration text+select fields.
3895       *
3896       * @param array $data Must be form 'v'=>xx, 'u'=>xx
3897       * @param string $query
3898       * @return string duration text+select fields and wrapping div(s)
3899       */
3900      public function output_html($data, $query='') {
3901          global $OUTPUT;
3902  
3903          $default = $this->get_defaultsetting();
3904          if (is_number($default)) {
3905              $defaultinfo = self::get_duration_text($default);
3906          } else if (is_array($default)) {
3907              $defaultinfo = self::get_duration_text($default['v']*$default['u']);
3908          } else {
3909              $defaultinfo = null;
3910          }
3911  
3912          $inputid = $this->get_id() . 'v';
3913          $units = self::get_units();
3914          $defaultunit = $this->defaultunit;
3915  
3916          $context = (object) [
3917              'id' => $this->get_id(),
3918              'name' => $this->get_full_name(),
3919              'value' => $data['v'],
3920              'readonly' => $this->is_readonly(),
3921              'options' => array_map(function($unit) use ($units, $data, $defaultunit) {
3922                  return [
3923                      'value' => $unit,
3924                      'name' => $units[$unit],
3925                      'selected' => ($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u']
3926                  ];
3927              }, array_keys($units))
3928          ];
3929  
3930          $element = $OUTPUT->render_from_template('core_admin/setting_configduration', $context);
3931  
3932          return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
3933      }
3934  }
3935  
3936  
3937  /**
3938   * Seconds duration setting with an advanced checkbox, that controls a additional
3939   * $name.'_adv' setting.
3940   *
3941   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3942   * @copyright 2014 The Open University
3943   */
3944  class admin_setting_configduration_with_advanced extends admin_setting_configduration {
3945      /**
3946       * Constructor
3947       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3948       *                     or 'myplugin/mysetting' for ones in config_plugins.
3949       * @param string $visiblename localised name
3950       * @param string $description localised long description
3951       * @param array  $defaultsetting array of int value, and bool whether it is
3952       *                     is advanced by default.
3953       * @param int $defaultunit - day, week, etc. (in seconds)
3954       */
3955      public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3956          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $defaultunit);
3957          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
3958      }
3959  }
3960  
3961  
3962  /**
3963   * Used to validate a textarea used for ip addresses
3964   *
3965   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3966   * @copyright 2011 Petr Skoda (http://skodak.org)
3967   */
3968  class admin_setting_configiplist extends admin_setting_configtextarea {
3969  
3970      /**
3971       * Validate the contents of the textarea as IP addresses
3972       *
3973       * Used to validate a new line separated list of IP addresses collected from
3974       * a textarea control
3975       *
3976       * @param string $data A list of IP Addresses separated by new lines
3977       * @return mixed bool true for success or string:error on failure
3978       */
3979      public function validate($data) {
3980          if(!empty($data)) {
3981              $lines = explode("\n", $data);
3982          } else {
3983              return true;
3984          }
3985          $result = true;
3986          $badips = array();
3987          foreach ($lines as $line) {
3988              $tokens = explode('#', $line);
3989              $ip = trim($tokens[0]);
3990              if (empty($ip)) {
3991                  continue;
3992              }
3993              if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
3994                  preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
3995                  preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
3996              } else {
3997                  $result = false;
3998                  $badips[] = $ip;
3999              }
4000          }
4001          if($result) {
4002              return true;
4003          } else {
4004              return get_string('validateiperror', 'admin', join(', ', $badips));
4005          }
4006      }
4007  }
4008  
4009  /**
4010   * Used to validate a textarea used for domain names, wildcard domain names and IP addresses/ranges (both IPv4 and IPv6 format).
4011   *
4012   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4013   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4014   */
4015  class admin_setting_configmixedhostiplist extends admin_setting_configtextarea {
4016  
4017      /**
4018       * Validate the contents of the textarea as either IP addresses, domain name or wildcard domain name (RFC 4592).
4019       * Used to validate a new line separated list of entries collected from a textarea control.
4020       *
4021       * This setting provides support for internationalised domain names (IDNs), however, such UTF-8 names will be converted to
4022       * their ascii-compatible encoding (punycode) on save, and converted back to their UTF-8 representation when fetched
4023       * via the get_setting() method, which has been overriden.
4024       *
4025       * @param string $data A list of FQDNs, DNS wildcard format domains, and IP addresses, separated by new lines.
4026       * @return mixed bool true for success or string:error on failure
4027       */
4028      public function validate($data) {
4029          if (empty($data)) {
4030              return true;
4031          }
4032          $entries = explode("\n", $data);
4033          $badentries = [];
4034  
4035          foreach ($entries as $key => $entry) {
4036              $entry = trim($entry);
4037              if (empty($entry)) {
4038                  return get_string('validateemptylineerror', 'admin');
4039              }
4040  
4041              // Validate each string entry against the supported formats.
4042              if (\core\ip_utils::is_ip_address($entry) || \core\ip_utils::is_ipv6_range($entry)
4043                      || \core\ip_utils::is_ipv4_range($entry) || \core\ip_utils::is_domain_name($entry)
4044                      || \core\ip_utils::is_domain_matching_pattern($entry)) {
4045                  continue;
4046              }
4047  
4048              // Otherwise, the entry is invalid.
4049              $badentries[] = $entry;
4050          }
4051  
4052          if ($badentries) {
4053              return get_string('validateerrorlist', 'admin', join(', ', $badentries));
4054          }
4055          return true;
4056      }
4057  
4058      /**
4059       * Convert any lines containing international domain names (IDNs) to their ascii-compatible encoding (ACE).
4060       *
4061       * @param string $data the setting data, as sent from the web form.
4062       * @return string $data the setting data, with all IDNs converted (using punycode) to their ascii encoded version.
4063       */
4064      protected function ace_encode($data) {
4065          if (empty($data)) {
4066              return $data;
4067          }
4068          $entries = explode("\n", $data);
4069          foreach ($entries as $key => $entry) {
4070              $entry = trim($entry);
4071              // This regex matches any string that has non-ascii character.
4072              if (preg_match('/[^\x00-\x7f]/', $entry)) {
4073                  // If we can convert the unicode string to an idn, do so.
4074                  // Otherwise, leave the original unicode string alone and let the validation function handle it (it will fail).
4075                  $val = idn_to_ascii($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4076                  $entries[$key] = $val ? $val : $entry;
4077              }
4078          }
4079          return implode("\n", $entries);
4080      }
4081  
4082      /**
4083       * Decode any ascii-encoded domain names back to their utf-8 representation for display.
4084       *
4085       * @param string $data the setting data, as found in the database.
4086       * @return string $data the setting data, with all ascii-encoded IDNs decoded back to their utf-8 representation.
4087       */
4088      protected function ace_decode($data) {
4089          $entries = explode("\n", $data);
4090          foreach ($entries as $key => $entry) {
4091              $entry = trim($entry);
4092              if (strpos($entry, 'xn--') !== false) {
4093                  $entries[$key] = idn_to_utf8($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4094              }
4095          }
4096          return implode("\n", $entries);
4097      }
4098  
4099      /**
4100       * Override, providing utf8-decoding for ascii-encoded IDN strings.
4101       *
4102       * @return mixed returns punycode-converted setting string if successful, else null.
4103       */
4104      public function get_setting() {
4105          // Here, we need to decode any ascii-encoded IDNs back to their native, utf-8 representation.
4106          $data = $this->config_read($this->name);
4107          if (function_exists('idn_to_utf8') && !is_null($data)) {
4108              $data = $this->ace_decode($data);
4109          }
4110          return $data;
4111      }
4112  
4113      /**
4114       * Override, providing ascii-encoding for utf8 (native) IDN strings.
4115       *
4116       * @param string $data
4117       * @return string
4118       */
4119      public function write_setting($data) {
4120          if ($this->paramtype === PARAM_INT and $data === '') {
4121              // Do not complain if '' used instead of 0.
4122              $data = 0;
4123          }
4124  
4125          // Try to convert any non-ascii domains to ACE prior to validation - we can't modify anything in validate!
4126          if (function_exists('idn_to_ascii')) {
4127              $data = $this->ace_encode($data);
4128          }
4129  
4130          $validated = $this->validate($data);
4131          if ($validated !== true) {
4132              return $validated;
4133          }
4134          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
4135      }
4136  }
4137  
4138  /**
4139   * Used to validate a textarea used for port numbers.
4140   *
4141   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4142   * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4143   */
4144  class admin_setting_configportlist extends admin_setting_configtextarea {
4145  
4146      /**
4147       * Validate the contents of the textarea as port numbers.
4148       * Used to validate a new line separated list of ports collected from a textarea control.
4149       *
4150       * @param string $data A list of ports separated by new lines
4151       * @return mixed bool true for success or string:error on failure
4152       */
4153      public function validate($data) {
4154          if (empty($data)) {
4155              return true;
4156          }
4157          $ports = explode("\n", $data);
4158          $badentries = [];
4159          foreach ($ports as $port) {
4160              $port = trim($port);
4161              if (empty($port)) {
4162                  return get_string('validateemptylineerror', 'admin');
4163              }
4164  
4165              // Is the string a valid integer number?
4166              if (strval(intval($port)) !== $port || intval($port) <= 0) {
4167                  $badentries[] = $port;
4168              }
4169          }
4170          if ($badentries) {
4171              return get_string('validateerrorlist', 'admin', $badentries);
4172          }
4173          return true;
4174      }
4175  }
4176  
4177  
4178  /**
4179   * An admin setting for selecting one or more users who have a capability
4180   * in the system context
4181   *
4182   * An admin setting for selecting one or more users, who have a particular capability
4183   * in the system context. Warning, make sure the list will never be too long. There is
4184   * no paging or searching of this list.
4185   *
4186   * To correctly get a list of users from this config setting, you need to call the
4187   * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
4188   *
4189   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4190   */
4191  class admin_setting_users_with_capability extends admin_setting_configmultiselect {
4192      /** @var string The capabilities name */
4193      protected $capability;
4194      /** @var int include admin users too */
4195      protected $includeadmins;
4196  
4197      /**
4198       * Constructor.
4199       *
4200       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
4201       * @param string $visiblename localised name
4202       * @param string $description localised long description
4203       * @param array $defaultsetting array of usernames
4204       * @param string $capability string capability name.
4205       * @param bool $includeadmins include administrators
4206       */
4207      function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
4208          $this->capability    = $capability;
4209          $this->includeadmins = $includeadmins;
4210          parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
4211      }
4212  
4213      /**
4214       * Load all of the uses who have the capability into choice array
4215       *
4216       * @return bool Always returns true
4217       */
4218      function load_choices() {
4219          if (is_array($this->choices)) {
4220              return true;
4221          }
4222          list($sort, $sortparams) = users_order_by_sql('u');
4223          if (!empty($sortparams)) {
4224              throw new coding_exception('users_order_by_sql returned some query parameters. ' .
4225                      'This is unexpected, and a problem because there is no way to pass these ' .
4226                      'parameters to get_users_by_capability. See MDL-34657.');
4227          }
4228          $userfields = 'u.id, u.username, ' . get_all_user_name_fields(true, 'u');
4229          $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort);
4230          $this->choices = array(
4231              '$@NONE@$' => get_string('nobody'),
4232              '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
4233          );
4234          if ($this->includeadmins) {
4235              $admins = get_admins();
4236              foreach ($admins as $user) {
4237                  $this->choices[$user->id] = fullname($user);
4238              }
4239          }
4240          if (is_array($users)) {
4241              foreach ($users as $user) {
4242                  $this->choices[$user->id] = fullname($user);
4243              }
4244          }
4245          return true;
4246      }
4247  
4248      /**
4249       * Returns the default setting for class
4250       *
4251       * @return mixed Array, or string. Empty string if no default
4252       */
4253      public function get_defaultsetting() {
4254          $this->load_choices();
4255          $defaultsetting = parent::get_defaultsetting();
4256          if (empty($defaultsetting)) {
4257              return array('$@NONE@$');
4258          } else if (array_key_exists($defaultsetting, $this->choices)) {
4259                  return $defaultsetting;
4260              } else {
4261                  return '';
4262              }
4263      }
4264  
4265      /**
4266       * Returns the current setting
4267       *
4268       * @return mixed array or string
4269       */
4270      public function get_setting() {
4271          $result = parent::get_setting();
4272          if ($result === null) {
4273              // this is necessary for settings upgrade
4274              return null;
4275          }
4276          if (empty($result)) {
4277              $result = array('$@NONE@$');
4278          }
4279          return $result;
4280      }
4281  
4282      /**
4283       * Save the chosen setting provided as $data
4284       *
4285       * @param array $data
4286       * @return mixed string or array
4287       */
4288      public function write_setting($data) {
4289      // If all is selected, remove any explicit options.
4290          if (in_array('$@ALL@$', $data)) {
4291              $data = array('$@ALL@$');
4292          }
4293          // None never needs to be written to the DB.
4294          if (in_array('$@NONE@$', $data)) {
4295              unset($data[array_search('$@NONE@$', $data)]);
4296          }
4297          return parent::write_setting($data);
4298      }
4299  }
4300  
4301  
4302  /**
4303   * Special checkbox for calendar - resets SESSION vars.
4304   *
4305   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4306   */
4307  class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
4308      /**
4309       * Calls the parent::__construct with default values
4310       *
4311       * name =>  calendar_adminseesall
4312       * visiblename => get_string('adminseesall', 'admin')
4313       * description => get_string('helpadminseesall', 'admin')
4314       * defaultsetting => 0
4315       */
4316      public function __construct() {
4317          parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
4318              get_string('helpadminseesall', 'admin'), '0');
4319      }
4320  
4321      /**
4322       * Stores the setting passed in $data
4323       *
4324       * @param mixed gets converted to string for comparison
4325       * @return string empty string or error message
4326       */
4327      public function write_setting($data) {
4328          global $SESSION;
4329          return parent::write_setting($data);
4330      }
4331  }
4332  
4333  /**
4334   * Special select for settings that are altered in setup.php and can not be altered on the fly
4335   *
4336   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4337   */
4338  class admin_setting_special_selectsetup extends admin_setting_configselect {
4339      /**
4340       * Reads the setting directly from the database
4341       *
4342       * @return mixed
4343       */
4344      public function get_setting() {
4345      // read directly from db!
4346          return get_config(NULL, $this->name);
4347      }
4348  
4349      /**
4350       * Save the setting passed in $data
4351       *
4352       * @param string $data The setting to save
4353       * @return string empty or error message
4354       */
4355      public function write_setting($data) {
4356          global $CFG;
4357          // do not change active CFG setting!
4358          $current = $CFG->{$this->name};
4359          $result = parent::write_setting($data);
4360          $CFG->{$this->name} = $current;
4361          return $result;
4362      }
4363  }
4364  
4365  
4366  /**
4367   * Special select for frontpage - stores data in course table
4368   *
4369   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4370   */
4371  class admin_setting_sitesetselect extends admin_setting_configselect {
4372      /**
4373       * Returns the site name for the selected site
4374       *
4375       * @see get_site()
4376       * @return string The site name of the selected site
4377       */
4378      public function get_setting() {
4379          $site = course_get_format(get_site())->get_course();
4380          return $site->{$this->name};
4381      }
4382  
4383      /**
4384       * Updates the database and save the setting
4385       *
4386       * @param string data
4387       * @return string empty or error message
4388       */
4389      public function write_setting($data) {
4390          global $DB, $SITE, $COURSE;
4391          if (!in_array($data, array_keys($this->choices))) {
4392              return get_string('errorsetting', 'admin');
4393          }
4394          $record = new stdClass();
4395          $record->id           = SITEID;
4396          $temp                 = $this->name;
4397          $record->$temp        = $data;
4398          $record->timemodified = time();
4399  
4400          course_get_format($SITE)->update_course_format_options($record);
4401          $DB->update_record('course', $record);
4402  
4403          // Reset caches.
4404          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4405          if ($SITE->id == $COURSE->id) {
4406              $COURSE = $SITE;
4407          }
4408          format_base::reset_course_cache($SITE->id);
4409  
4410          return '';
4411  
4412      }
4413  }
4414  
4415  
4416  /**
4417   * Select for blog's bloglevel setting: if set to 0, will set blog_menu
4418   * block to hidden.
4419   *
4420   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4421   */
4422  class admin_setting_bloglevel extends admin_setting_configselect {
4423      /**
4424       * Updates the database and save the setting
4425       *
4426       * @param string data
4427       * @return string empty or error message
4428       */
4429      public function write_setting($data) {
4430          global $DB, $CFG;
4431          if ($data == 0) {
4432              $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
4433              foreach ($blogblocks as $block) {
4434                  $DB->set_field('block', 'visible', 0, array('id' => $block->id));
4435              }
4436          } else {
4437              // reenable all blocks only when switching from disabled blogs
4438              if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
4439                  $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
4440                  foreach ($blogblocks as $block) {
4441                      $DB->set_field('block', 'visible', 1, array('id' => $block->id));
4442                  }
4443              }
4444          }
4445          return parent::write_setting($data);
4446      }
4447  }
4448  
4449  
4450  /**
4451   * Special select - lists on the frontpage - hacky
4452   *
4453   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4454   */
4455  class admin_setting_courselist_frontpage extends admin_setting {
4456      /** @var array Array of choices value=>label */
4457      public $choices;
4458  
4459      /**
4460       * Construct override, requires one param
4461       *
4462       * @param bool $loggedin Is the user logged in
4463       */
4464      public function __construct($loggedin) {
4465          global $CFG;
4466          require_once($CFG->dirroot.'/course/lib.php');
4467          $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
4468          $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
4469          $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
4470          $defaults    = array(FRONTPAGEALLCOURSELIST);
4471          parent::__construct($name, $visiblename, $description, $defaults);
4472      }
4473  
4474      /**
4475       * Loads the choices available
4476       *
4477       * @return bool always returns true
4478       */
4479      public function load_choices() {
4480          if (is_array($this->choices)) {
4481              return true;
4482          }
4483          $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
4484              FRONTPAGEALLCOURSELIST => get_string('frontpagecourselist'),
4485              FRONTPAGEENROLLEDCOURSELIST => get_string('frontpageenrolledcourselist'),
4486              FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
4487              FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
4488              FRONTPAGECOURSESEARCH  => get_string('frontpagecoursesearch'),
4489              'none'                 => get_string('none'));
4490          if ($this->name === 'frontpage') {
4491              unset($this->choices[FRONTPAGEENROLLEDCOURSELIST]);
4492          }
4493          return true;
4494      }
4495  
4496      /**
4497       * Returns the selected settings
4498       *
4499       * @param mixed array or setting or null
4500       */
4501      public function get_setting() {
4502          $result = $this->config_read($this->name);
4503          if (is_null($result)) {
4504              return NULL;
4505          }
4506          if ($result === '') {
4507              return array();
4508          }
4509          return explode(',', $result);
4510      }
4511  
4512      /**
4513       * Save the selected options
4514       *
4515       * @param array $data
4516       * @return mixed empty string (data is not an array) or bool true=success false=failure
4517       */
4518      public function write_setting($data) {
4519          if (!is_array($data)) {
4520              return '';
4521          }
4522          $this->load_choices();
4523          $save = array();
4524          foreach($data as $datum) {
4525              if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
4526                  continue;
4527              }
4528              $save[$datum] = $datum; // no duplicates
4529          }
4530          return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
4531      }
4532  
4533      /**
4534       * Return XHTML select field and wrapping div
4535       *
4536       * @todo Add vartype handling to make sure $data is an array
4537       * @param array $data Array of elements to select by default
4538       * @return string XHTML select field and wrapping div
4539       */
4540      public function output_html($data, $query='') {
4541          global $OUTPUT;
4542  
4543          $this->load_choices();
4544          $currentsetting = array();
4545          foreach ($data as $key) {
4546              if ($key != 'none' and array_key_exists($key, $this->choices)) {
4547                  $currentsetting[] = $key; // already selected first
4548              }
4549          }
4550  
4551          $context = (object) [
4552              'id' => $this->get_id(),
4553              'name' => $this->get_full_name(),
4554          ];
4555  
4556          $options = $this->choices;
4557          $selects = [];
4558          for ($i = 0; $i < count($this->choices) - 1; $i++) {
4559              if (!array_key_exists($i, $currentsetting)) {
4560                  $currentsetting[$i] = 'none';
4561              }
4562              $selects[] = [
4563                  'key' => $i,
4564                  'options' => array_map(function($option) use ($options, $currentsetting, $i) {
4565                      return [
4566                          'name' => $options[$option],
4567                          'value' => $option,
4568                          'selected' => $currentsetting[$i] == $option
4569                      ];
4570                  }, array_keys($options))
4571              ];
4572          }
4573          $context->selects = $selects;
4574  
4575          $element = $OUTPUT->render_from_template('core_admin/setting_courselist_frontpage', $context);
4576  
4577          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
4578      }
4579  }
4580  
4581  
4582  /**
4583   * Special checkbox for frontpage - stores data in course table
4584   *
4585   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4586   */
4587  class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
4588      /**
4589       * Returns the current sites name
4590       *
4591       * @return string
4592       */
4593      public function get_setting() {
4594          $site = course_get_format(get_site())->get_course();
4595          return $site->{$this->name};
4596      }
4597  
4598      /**
4599       * Save the selected setting
4600       *
4601       * @param string $data The selected site
4602       * @return string empty string or error message
4603       */
4604      public function write_setting($data) {
4605          global $DB, $SITE, $COURSE;
4606          $record = new stdClass();
4607          $record->id            = $SITE->id;
4608          $record->{$this->name} = ($data == '1' ? 1 : 0);
4609          $record->timemodified  = time();
4610  
4611          course_get_format($SITE)->update_course_format_options($record);
4612          $DB->update_record('course', $record);
4613  
4614          // Reset caches.
4615          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4616          if ($SITE->id == $COURSE->id) {
4617              $COURSE = $SITE;
4618          }
4619          format_base::reset_course_cache($SITE->id);
4620  
4621          return '';
4622      }
4623  }
4624  
4625  /**
4626   * Special text for frontpage - stores data in course table.
4627   * Empty string means not set here. Manual setting is required.
4628   *
4629   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4630   */
4631  class admin_setting_sitesettext extends admin_setting_configtext {
4632  
4633      /**
4634       * Constructor.
4635       */
4636      public function __construct() {
4637          call_user_func_array(['parent', '__construct'], func_get_args());
4638          $this->set_force_ltr(false);
4639      }
4640  
4641      /**
4642       * Return the current setting
4643       *
4644       * @return mixed string or null
4645       */
4646      public function get_setting() {
4647          $site = course_get_format(get_site())->get_course();
4648          return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
4649      }
4650  
4651      /**
4652       * Validate the selected data
4653       *
4654       * @param string $data The selected value to validate
4655       * @return mixed true or message string
4656       */
4657      public function validate($data) {
4658          global $DB, $SITE;
4659          $cleaned = clean_param($data, PARAM_TEXT);
4660          if ($cleaned === '') {
4661              return get_string('required');
4662          }
4663          if ($this->name ==='shortname' &&
4664                  $DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data, $SITE->id))) {
4665              return get_string('shortnametaken', 'error', $data);
4666          }
4667          if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
4668              return true;
4669          } else {
4670              return get_string('validateerror', 'admin');
4671          }
4672      }
4673  
4674      /**
4675       * Save the selected setting
4676       *
4677       * @param string $data The selected value
4678       * @return string empty or error message
4679       */
4680      public function write_setting($data) {
4681          global $DB, $SITE, $COURSE;
4682          $data = trim($data);
4683          $validated = $this->validate($data);
4684          if ($validated !== true) {
4685              return $validated;
4686          }
4687  
4688          $record = new stdClass();
4689          $record->id            = $SITE->id;
4690          $record->{$this->name} = $data;
4691          $record->timemodified  = time();
4692  
4693          course_get_format($SITE)->update_course_format_options($record);
4694          $DB->update_record('course', $record);
4695  
4696          // Reset caches.
4697          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4698          if ($SITE->id == $COURSE->id) {
4699              $COURSE = $SITE;
4700          }
4701          format_base::reset_course_cache($SITE->id);
4702  
4703          return '';
4704      }
4705  }
4706  
4707  
4708  /**
4709   * Special text editor for site description.
4710   *
4711   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4712   */
4713  class admin_setting_special_frontpagedesc extends admin_setting_confightmleditor {
4714  
4715      /**
4716       * Calls parent::__construct with specific arguments
4717       */
4718      public function __construct() {
4719          parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), null,
4720              PARAM_RAW, 60, 15);
4721      }
4722  
4723      /**
4724       * Return the current setting
4725       * @return string The current setting
4726       */
4727      public function get_setting() {
4728          $site = course_get_format(get_site())->get_course();
4729          return $site->{$this->name};
4730      }
4731  
4732      /**
4733       * Save the new setting
4734       *
4735       * @param string $data The new value to save
4736       * @return string empty or error message
4737       */
4738      public function write_setting($data) {
4739          global $DB, $SITE, $COURSE;
4740          $record = new stdClass();
4741          $record->id            = $SITE->id;
4742          $record->{$this->name} = $data;
4743          $record->timemodified  = time();
4744  
4745          course_get_format($SITE)->update_course_format_options($record);
4746          $DB->update_record('course', $record);
4747  
4748          // Reset caches.
4749          $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
4750          if ($SITE->id == $COURSE->id) {
4751              $COURSE = $SITE;
4752          }
4753          format_base::reset_course_cache($SITE->id);
4754  
4755          return '';
4756      }
4757  }
4758  
4759  
4760  /**
4761   * Administration interface for emoticon_manager settings.
4762   *
4763   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4764   */
4765  class admin_setting_emoticons extends admin_setting {
4766  
4767      /**
4768       * Calls parent::__construct with specific args
4769       */
4770      public function __construct() {
4771          global $CFG;
4772  
4773          $manager = get_emoticon_manager();
4774          $defaults = $this->prepare_form_data($manager->default_emoticons());
4775          parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
4776      }
4777  
4778      /**
4779       * Return the current setting(s)
4780       *
4781       * @return array Current settings array
4782       */
4783      public function get_setting() {
4784          global $CFG;
4785  
4786          $manager = get_emoticon_manager();
4787  
4788          $config = $this->config_read($this->name);
4789          if (is_null($config)) {
4790              return null;
4791          }
4792  
4793          $config = $manager->decode_stored_config($config);
4794          if (is_null($config)) {
4795              return null;
4796          }
4797  
4798          return $this->prepare_form_data($config);
4799      }
4800  
4801      /**
4802       * Save selected settings
4803       *
4804       * @param array $data Array of settings to save
4805       * @return bool
4806       */
4807      public function write_setting($data) {
4808  
4809          $manager = get_emoticon_manager();
4810          $emoticons = $this->process_form_data($data);
4811  
4812          if ($emoticons === false) {
4813              return false;
4814          }
4815  
4816          if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
4817              return ''; // success
4818          } else {
4819              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
4820          }
4821      }
4822  
4823      /**
4824       * Return XHTML field(s) for options
4825       *
4826       * @param array $data Array of options to set in HTML
4827       * @return string XHTML string for the fields and wrapping div(s)
4828       */
4829      public function output_html($data, $query='') {
4830          global $OUTPUT;
4831  
4832          $context = (object) [
4833              'name' => $this->get_full_name(),
4834              'emoticons' => [],
4835              'forceltr' => true,
4836          ];
4837  
4838          $i = 0;
4839          foreach ($data as $field => $value) {
4840  
4841              // When $i == 0: text.
4842              // When $i == 1: imagename.
4843              // When $i == 2: imagecomponent.
4844              // When $i == 3: altidentifier.
4845              // When $i == 4: altcomponent.
4846              $fields[$i] = (object) [
4847                  'field' => $field,
4848                  'value' => $value,
4849                  'index' => $i
4850              ];
4851              $i++;
4852  
4853              if ($i > 4) {
4854                  $icon = null;
4855                  if (!empty($fields[1]->value)) {
4856                      if (get_string_manager()->string_exists($fields[3]->value, $fields[4]->value)) {
4857                          $alt = get_string($fields[3]->value, $fields[4]->value);
4858                      } else {
4859                          $alt = $fields[0]->value;
4860                      }
4861                      $icon = new pix_emoticon($fields[1]->value, $alt, $fields[2]->value);
4862                  }
4863                  $context->emoticons[] = [
4864                      'fields' => $fields,
4865                      'icon' => $icon ? $icon->export_for_template($OUTPUT) : null
4866                  ];
4867                  $fields = [];
4868                  $i = 0;
4869              }
4870          }
4871  
4872          $context->reseturl = new moodle_url('/admin/resetemoticons.php');
4873          $element = $OUTPUT->render_from_template('core_admin/setting_emoticons', $context);
4874          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
4875      }
4876  
4877      /**
4878       * Converts the array of emoticon objects provided by {@see emoticon_manager} into admin settings form data
4879       *
4880       * @see self::process_form_data()
4881       * @param array $emoticons array of emoticon objects as returned by {@see emoticon_manager}
4882       * @return array of form fields and their values
4883       */
4884      protected function prepare_form_data(array $emoticons) {
4885  
4886          $form = array();
4887          $i = 0;
4888          foreach ($emoticons as $emoticon) {
4889              $form['text'.$i]            = $emoticon->text;
4890              $form['imagename'.$i]       = $emoticon->imagename;
4891              $form['imagecomponent'.$i]  = $emoticon->imagecomponent;
4892              $form['altidentifier'.$i]   = $emoticon->altidentifier;
4893              $form['altcomponent'.$i]    = $emoticon->altcomponent;
4894              $i++;
4895          }
4896          // add one more blank field set for new object
4897          $form['text'.$i]            = '';
4898          $form['imagename'.$i]       = '';
4899          $form['imagecomponent'.$i]  = '';
4900          $form['altidentifier'.$i]   = '';
4901          $form['altcomponent'.$i]    = '';
4902  
4903          return $form;
4904      }
4905  
4906      /**
4907       * Converts the data from admin settings form into an array of emoticon objects
4908       *
4909       * @see self::prepare_form_data()
4910       * @param array $data array of admin form fields and values
4911       * @return false|array of emoticon objects
4912       */
4913      protected function process_form_data(array $form) {
4914  
4915          $count = count($form); // number of form field values
4916  
4917          if ($count % 5) {
4918              // we must get five fields per emoticon object
4919              return false;
4920          }
4921  
4922          $emoticons = array();
4923          for ($i = 0; $i < $count / 5; $i++) {
4924              $emoticon                   = new stdClass();
4925              $emoticon->text             = clean_param(trim($form['text'.$i]), PARAM_NOTAGS);
4926              $emoticon->imagename        = clean_param(trim($form['imagename'.$i]), PARAM_PATH);
4927              $emoticon->imagecomponent   = clean_param(trim($form['imagecomponent'.$i]), PARAM_COMPONENT);
4928              $emoticon->altidentifier    = clean_param(trim($form['altidentifier'.$i]), PARAM_STRINGID);
4929              $emoticon->altcomponent     = clean_param(trim($form['altcomponent'.$i]), PARAM_COMPONENT);
4930  
4931              if (strpos($emoticon->text, ':/') !== false or strpos($emoticon->text, '//') !== false) {
4932                  // prevent from breaking http://url.addresses by accident
4933                  $emoticon->text = '';
4934              }
4935  
4936              if (strlen($emoticon->text) < 2) {
4937                  // do not allow single character emoticons
4938                  $emoticon->text = '';
4939              }
4940  
4941              if (preg_match('/^[a-zA-Z]+[a-zA-Z0-9]*$/', $emoticon->text)) {
4942                  // emoticon text must contain some non-alphanumeric character to prevent
4943                  // breaking HTML tags
4944                  $emoticon->text = '';
4945              }
4946  
4947              if ($emoticon->text !== '' and $emoticon->imagename !== '' and $emoticon->imagecomponent !== '') {
4948                  $emoticons[] = $emoticon;
4949              }
4950          }
4951          return $emoticons;
4952      }
4953  
4954  }
4955  
4956  
4957  /**
4958   * Special setting for limiting of the list of available languages.
4959   *
4960   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4961   */
4962  class admin_setting_langlist extends admin_setting_configtext {
4963      /**
4964       * Calls parent::__construct with specific arguments
4965       */
4966      public function __construct() {
4967          parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
4968      }
4969  
4970      /**
4971       * Validate that each language identifier exists on the site
4972       *
4973       * @param string $data
4974       * @return bool|string True if validation successful, otherwise error string
4975       */
4976      public function validate($data) {
4977          $parentcheck = parent::validate($data);
4978          if ($parentcheck !== true) {
4979              return $parentcheck;
4980          }
4981  
4982          if ($data === '') {
4983              return true;
4984          }
4985  
4986          // Normalize language identifiers.
4987          $langcodes = array_map('trim', explode(',', $data));
4988          foreach ($langcodes as $langcode) {
4989              // If the langcode contains optional alias, split it out.
4990              [$langcode, ] = preg_split('/\s*\|\s*/', $langcode, 2);
4991  
4992              if (!get_string_manager()->translation_exists($langcode)) {
4993                  return get_string('invalidlanguagecode', 'error', $langcode);
4994              }
4995          }
4996  
4997          return true;
4998      }
4999  
5000      /**
5001       * Save the new setting
5002       *
5003       * @param string $data The new setting
5004       * @return bool
5005       */
5006      public function write_setting($data) {
5007          $return = parent::write_setting($data);
5008          get_string_manager()->reset_caches();
5009          return $return;
5010      }
5011  }
5012  
5013  
5014  /**
5015   * Allows to specify comma separated list of known country codes.
5016   *
5017   * This is a simple subclass of the plain input text field with added validation so that all the codes are actually
5018   * known codes.
5019   *
5020   * @package     core
5021   * @category    admin
5022   * @copyright   2020 David Mudrák <david@moodle.com>
5023   * @license     https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5024   */
5025  class admin_setting_countrycodes extends admin_setting_configtext {
5026  
5027      /**
5028       * Construct the instance of the setting.
5029       *
5030       * @param string $name Name of the admin setting such as 'allcountrycodes' or 'myplugin/countries'.
5031       * @param lang_string|string $visiblename Language string with the field label text.
5032       * @param lang_string|string $description Language string with the field description text.
5033       * @param string $defaultsetting Default value of the setting.
5034       * @param int $size Input text field size.
5035       */
5036      public function __construct($name, $visiblename, $description, $defaultsetting = '', $size = null) {
5037          parent::__construct($name, $visiblename, $description, $defaultsetting, '/^(?:\w+(?:,\w+)*)?$/', $size);
5038      }
5039  
5040      /**
5041       * Validate the setting value before storing it.
5042       *
5043       * The value is first validated through custom regex so that it is a word consisting of letters, numbers or underscore; or
5044       * a comma separated list of such words.
5045       *
5046       * @param string $data Value inserted into the setting field.
5047       * @return bool|string True if the value is OK, error string otherwise.
5048       */
5049      public function validate($data) {
5050  
5051          $parentcheck = parent::validate($data);
5052  
5053          if ($parentcheck !== true) {
5054              return $parentcheck;
5055          }
5056  
5057          if ($data === '') {
5058              return true;
5059          }
5060  
5061          $allcountries = get_string_manager()->get_list_of_countries(true);
5062  
5063          foreach (explode(',', $data) as $code) {
5064              if (!isset($allcountries[$code])) {
5065                  return get_string('invalidcountrycode', 'core_error', $code);
5066              }
5067          }
5068  
5069          return true;
5070      }
5071  }
5072  
5073  
5074  /**
5075   * Selection of one of the recognised countries using the list
5076   * returned by {@link get_list_of_countries()}.
5077   *
5078   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5079   */
5080  class admin_settings_country_select extends admin_setting_configselect {
5081      protected $includeall;
5082      public function __construct($name, $visiblename, $description, $defaultsetting, $includeall=false) {
5083          $this->includeall = $includeall;
5084          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
5085      }
5086  
5087      /**
5088       * Lazy-load the available choices for the select box
5089       */
5090      public function load_choices() {
5091          global $CFG;
5092          if (is_array($this->choices)) {
5093              return true;
5094          }
5095          $this->choices = array_merge(
5096                  array('0' => get_string('choosedots')),
5097                  get_string_manager()->get_list_of_countries($this->includeall));
5098          return true;
5099      }
5100  }
5101  
5102  
5103  /**
5104   * admin_setting_configselect for the default number of sections in a course,
5105   * simply so we can lazy-load the choices.
5106   *
5107   * @copyright 2011 The Open University
5108   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5109   */
5110  class admin_settings_num_course_sections extends admin_setting_configselect {
5111      public function __construct($name, $visiblename, $description, $defaultsetting) {
5112          parent::__construct($name, $visiblename, $description, $defaultsetting, array());
5113      }
5114  
5115      /** Lazy-load the available choices for the select box */
5116      public function load_choices() {
5117          $max = get_config('moodlecourse', 'maxsections');
5118          if (!isset($max) || !is_numeric($max)) {
5119              $max = 52;
5120          }
5121          for ($i = 0; $i <= $max; $i++) {
5122              $this->choices[$i] = "$i";
5123          }
5124          return true;
5125      }
5126  }
5127  
5128  
5129  /**
5130   * Course category selection
5131   *
5132   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5133   */
5134  class admin_settings_coursecat_select extends admin_setting_configselect_autocomplete {
5135      /**
5136       * Calls parent::__construct with specific arguments
5137       */
5138      public function __construct($name, $visiblename, $description, $defaultsetting = 1) {
5139          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices = null);
5140      }
5141  
5142      /**
5143       * Load the available choices for the select box
5144       *
5145       * @return bool
5146       */
5147      public function load_choices() {
5148          if (is_array($this->choices)) {
5149              return true;
5150          }
5151          $this->choices = core_course_category::make_categories_list('', 0, ' / ');
5152          return true;
5153      }
5154  }
5155  
5156  
5157  /**
5158   * Special control for selecting days to backup
5159   *
5160   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5161   */
5162  class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
5163      /**
5164       * Calls parent::__construct with specific arguments
5165       */
5166      public function __construct() {
5167          parent::__construct('backup_auto_weekdays', get_string('automatedbackupschedule','backup'), get_string('automatedbackupschedulehelp','backup'), array(), NULL);
5168          $this->plugin = 'backup';
5169      }
5170  
5171      /**
5172       * Load the available choices for the select box
5173       *
5174       * @return bool Always returns true
5175       */
5176      public function load_choices() {
5177          if (is_array($this->choices)) {
5178              return true;
5179          }
5180          $this->choices = array();
5181          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5182          foreach ($days as $day) {
5183              $this->choices[$day] = get_string($day, 'calendar');
5184          }
5185          return true;
5186      }
5187  }
5188  
5189  /**
5190   * Special setting for backup auto destination.
5191   *
5192   * @package    core
5193   * @subpackage admin
5194   * @copyright  2014 Frédéric Massart - FMCorz.net
5195   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5196   */
5197  class admin_setting_special_backup_auto_destination extends admin_setting_configdirectory {
5198  
5199      /**
5200       * Calls parent::__construct with specific arguments.
5201       */
5202      public function __construct() {
5203          parent::__construct('backup/backup_auto_destination', new lang_string('saveto'), new lang_string('backupsavetohelp'), '');
5204      }
5205  
5206      /**
5207       * Check if the directory must be set, depending on backup/backup_auto_storage.
5208       *
5209       * Note: backup/backup_auto_storage must be specified BEFORE this setting otherwise
5210       * there will be conflicts if this validation happens before the other one.
5211       *
5212       * @param string $data Form data.
5213       * @return string Empty when no errors.
5214       */
5215      public function write_setting($data) {
5216          $storage = (int) get_config('backup', 'backup_auto_storage');
5217          if ($storage !== 0) {
5218              if (empty($data) || !file_exists($data) || !is_dir($data) || !is_writable($data) ) {
5219                  // The directory must exist and be writable.
5220                  return get_string('backuperrorinvaliddestination');
5221              }
5222          }
5223          return parent::write_setting($data);
5224      }
5225  }
5226  
5227  
5228  /**
5229   * Special debug setting
5230   *
5231   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5232   */
5233  class admin_setting_special_debug extends admin_setting_configselect {
5234      /**
5235       * Calls parent::__construct with specific arguments
5236       */
5237      public function __construct() {
5238          parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
5239      }
5240  
5241      /**
5242       * Load the available choices for the select box
5243       *
5244       * @return bool
5245       */
5246      public function load_choices() {
5247          if (is_array($this->choices)) {
5248              return true;
5249          }
5250          $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
5251              DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
5252              DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
5253              DEBUG_ALL       => get_string('debugall', 'admin'),
5254              DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
5255          return true;
5256      }
5257  }
5258  
5259  
5260  /**
5261   * Special admin control
5262   *
5263   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5264   */
5265  class admin_setting_special_calendar_weekend extends admin_setting {
5266      /**
5267       * Calls parent::__construct with specific arguments
5268       */
5269      public function __construct() {
5270          $name = 'calendar_weekend';
5271          $visiblename = get_string('calendar_weekend', 'admin');
5272          $description = get_string('helpweekenddays', 'admin');
5273          $default = array ('0', '6'); // Saturdays and Sundays
5274          parent::__construct($name, $visiblename, $description, $default);
5275      }
5276  
5277      /**
5278       * Gets the current settings as an array
5279       *
5280       * @return mixed Null if none, else array of settings
5281       */
5282      public function get_setting() {
5283          $result = $this->config_read($this->name);
5284          if (is_null($result)) {
5285              return NULL;
5286          }
5287          if ($result === '') {
5288              return array();
5289          }
5290          $settings = array();
5291          for ($i=0; $i<7; $i++) {
5292              if ($result & (1 << $i)) {
5293                  $settings[] = $i;
5294              }
5295          }
5296          return $settings;
5297      }
5298  
5299      /**
5300       * Save the new settings
5301       *
5302       * @param array $data Array of new settings
5303       * @return bool
5304       */
5305      public function write_setting($data) {
5306          if (!is_array($data)) {
5307              return '';
5308          }
5309          unset($data['xxxxx']);
5310          $result = 0;
5311          foreach($data as $index) {
5312              $result |= 1 << $index;
5313          }
5314          return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
5315      }
5316  
5317      /**
5318       * Return XHTML to display the control
5319       *
5320       * @param array $data array of selected days
5321       * @param string $query
5322       * @return string XHTML for display (field + wrapping div(s)
5323       */
5324      public function output_html($data, $query='') {
5325          global $OUTPUT;
5326  
5327          // The order matters very much because of the implied numeric keys.
5328          $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
5329          $context = (object) [
5330              'name' => $this->get_full_name(),
5331              'id' => $this->get_id(),
5332              'days' => array_map(function($index) use ($days, $data) {
5333                  return [
5334                      'index' => $index,
5335                      'label' => get_string($days[$index], 'calendar'),
5336                      'checked' => in_array($index, $data)
5337                  ];
5338              }, array_keys($days))
5339          ];
5340  
5341          $element = $OUTPUT->render_from_template('core_admin/setting_special_calendar_weekend', $context);
5342  
5343          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', NULL, $query);
5344  
5345      }
5346  }
5347  
5348  
5349  /**
5350   * Admin setting that allows a user to pick a behaviour.
5351   *
5352   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5353   */
5354  class admin_setting_question_behaviour extends admin_setting_configselect {
5355      /**
5356       * @param string $name name of config variable
5357       * @param string $visiblename display name
5358       * @param string $description description
5359       * @param string $default default.
5360       */
5361      public function __construct($name, $visiblename, $description, $default) {
5362          parent::__construct($name, $visiblename, $description, $default, null);
5363      }
5364  
5365      /**
5366       * Load list of behaviours as choices
5367       * @return bool true => success, false => error.
5368       */
5369      public function load_choices() {
5370          global $CFG;
5371          require_once($CFG->dirroot . '/question/engine/lib.php');
5372          $this->choices = question_engine::get_behaviour_options('');
5373          return true;
5374      }
5375  }
5376  
5377  
5378  /**
5379   * Admin setting that allows a user to pick appropriate roles for something.
5380   *
5381   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5382   */
5383  class admin_setting_pickroles extends admin_setting_configmulticheckbox {
5384      /** @var array Array of capabilities which identify roles */
5385      private $types;
5386  
5387      /**
5388       * @param string $name Name of config variable
5389       * @param string $visiblename Display name
5390       * @param string $description Description
5391       * @param array $types Array of archetypes which identify
5392       *              roles that will be enabled by default.
5393       */
5394      public function __construct($name, $visiblename, $description, $types) {
5395          parent::__construct($name, $visiblename, $description, NULL, NULL);
5396          $this->types = $types;
5397      }
5398  
5399      /**
5400       * Load roles as choices
5401       *
5402       * @return bool true=>success, false=>error
5403       */
5404      public function load_choices() {
5405          global $CFG, $DB;
5406          if (during_initial_install()) {
5407              return false;
5408          }
5409          if (is_array($this->choices)) {
5410              return true;
5411          }
5412          if ($roles = get_all_roles()) {
5413              $this->choices = role_fix_names($roles, null, ROLENAME_ORIGINAL, true);
5414              return true;
5415          } else {
5416              return false;
5417          }
5418      }
5419  
5420      /**
5421       * Return the default setting for this control
5422       *
5423       * @return array Array of default settings
5424       */
5425      public function get_defaultsetting() {
5426          global $CFG;
5427  
5428          if (during_initial_install()) {
5429              return null;
5430          }
5431          $result = array();
5432          foreach($this->types as $archetype) {
5433              if ($caproles = get_archetype_roles($archetype)) {
5434                  foreach ($caproles as $caprole) {
5435                      $result[$caprole->id] = 1;
5436                  }
5437              }
5438          }
5439          return $result;
5440      }
5441  }
5442  
5443  
5444  /**
5445   * Admin setting that is a list of installed filter plugins.
5446   *
5447   * @copyright 2015 The Open University
5448   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5449   */
5450  class admin_setting_pickfilters extends admin_setting_configmulticheckbox {
5451  
5452      /**
5453       * Constructor
5454       *
5455       * @param string $name unique ascii name, either 'mysetting' for settings
5456       *      that in config, or 'myplugin/mysetting' for ones in config_plugins.
5457       * @param string $visiblename localised name
5458       * @param string $description localised long description
5459       * @param array $default the default. E.g. array('urltolink' => 1, 'emoticons' => 1)
5460       */
5461      public function __construct($name, $visiblename, $description, $default) {
5462          if (empty($default)) {
5463              $default = array();
5464          }
5465          $this->load_choices();
5466          foreach ($default as $plugin) {
5467              if (!isset($this->choices[$plugin])) {
5468                  unset($default[$plugin]);
5469              }
5470          }
5471          parent::__construct($name, $visiblename, $description, $default, null);
5472      }
5473  
5474      public function load_choices() {
5475          if (is_array($this->choices)) {
5476              return true;
5477          }
5478          $this->choices = array();
5479  
5480          foreach (core_component::get_plugin_list('filter') as $plugin => $unused) {
5481              $this->choices[$plugin] = filter_get_name($plugin);
5482          }
5483          return true;
5484      }
5485  }
5486  
5487  
5488  /**
5489   * Text field with an advanced checkbox, that controls a additional $name.'_adv' setting.
5490   *
5491   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5492   */
5493  class admin_setting_configtext_with_advanced extends admin_setting_configtext {
5494      /**
5495       * Constructor
5496       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5497       * @param string $visiblename localised
5498       * @param string $description long localised info
5499       * @param array $defaultsetting ('value'=>string, '__construct'=>bool)
5500       * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
5501       * @param int $size default field size
5502       */
5503      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
5504          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $paramtype, $size);
5505          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5506      }
5507  }
5508  
5509  
5510  /**
5511   * Checkbox with an advanced checkbox that controls an additional $name.'_adv' config setting.
5512   *
5513   * @copyright 2009 Petr Skoda (http://skodak.org)
5514   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5515   */
5516  class admin_setting_configcheckbox_with_advanced extends admin_setting_configcheckbox {
5517  
5518      /**
5519       * Constructor
5520       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5521       * @param string $visiblename localised
5522       * @param string $description long localised info
5523       * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
5524       * @param string $yes value used when checked
5525       * @param string $no value used when not checked
5526       */
5527      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5528          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5529          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5530      }
5531  
5532  }
5533  
5534  
5535  /**
5536   * Checkbox with an advanced checkbox that controls an additional $name.'_locked' config setting.
5537   *
5538   * This is nearly a copy/paste of admin_setting_configcheckbox_with_adv
5539   *
5540   * @copyright 2010 Sam Hemelryk
5541   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5542   */
5543  class admin_setting_configcheckbox_with_lock extends admin_setting_configcheckbox {
5544      /**
5545       * Constructor
5546       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
5547       * @param string $visiblename localised
5548       * @param string $description long localised info
5549       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5550       * @param string $yes value used when checked
5551       * @param string $no value used when not checked
5552       */
5553      public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
5554          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $yes, $no);
5555          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5556      }
5557  
5558  }
5559  
5560  /**
5561   * Autocomplete as you type form element.
5562   *
5563   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5564   */
5565  class admin_setting_configselect_autocomplete extends admin_setting_configselect {
5566      /** @var boolean $tags Should we allow typing new entries to the field? */
5567      protected $tags = false;
5568      /** @var string $ajax Name of an AMD module to send/process ajax requests. */
5569      protected $ajax = '';
5570      /** @var string $placeholder Placeholder text for an empty list. */
5571      protected $placeholder = '';
5572      /** @var bool $casesensitive Whether the search has to be case-sensitive. */
5573      protected $casesensitive = false;
5574      /** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
5575      protected $showsuggestions = true;
5576      /** @var string $noselectionstring String that is shown when there are no selections. */
5577      protected $noselectionstring = '';
5578  
5579      /**
5580       * Returns XHTML select field and wrapping div(s)
5581       *
5582       * @see output_select_html()
5583       *
5584       * @param string $data the option to show as selected
5585       * @param string $query
5586       * @return string XHTML field and wrapping div
5587       */
5588      public function output_html($data, $query='') {
5589          global $PAGE;
5590  
5591          $html = parent::output_html($data, $query);
5592  
5593          if ($html === '') {
5594              return $html;
5595          }
5596  
5597          $this->placeholder = get_string('search');
5598  
5599          $params = array('#' . $this->get_id(), $this->tags, $this->ajax,
5600              $this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring);
5601  
5602          // Load autocomplete wrapper for select2 library.
5603          $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params);
5604  
5605          return $html;
5606      }
5607  }
5608  
5609  /**
5610   * Dropdown menu with an advanced checkbox, that controls a additional $name.'_adv' setting.
5611   *
5612   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5613   */
5614  class admin_setting_configselect_with_advanced extends admin_setting_configselect {
5615      /**
5616       * Calls parent::__construct with specific arguments
5617       */
5618      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5619          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5620          $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
5621      }
5622  
5623  }
5624  
5625  /**
5626   * Select with an advanced checkbox that controls an additional $name.'_locked' config setting.
5627   *
5628   * @copyright 2017 Marina Glancy
5629   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5630   */
5631  class admin_setting_configselect_with_lock extends admin_setting_configselect {
5632      /**
5633       * Constructor
5634       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
5635       *     or 'myplugin/mysetting' for ones in config_plugins.
5636       * @param string $visiblename localised
5637       * @param string $description long localised info
5638       * @param array $defaultsetting ('value'=>string, 'locked'=>bool)
5639       * @param array $choices array of $value=>$label for each selection
5640       */
5641      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5642          parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $choices);
5643          $this->set_locked_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['locked']));
5644      }
5645  }
5646  
5647  
5648  /**
5649   * Graded roles in gradebook
5650   *
5651   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5652   */
5653  class admin_setting_special_gradebookroles extends admin_setting_pickroles {
5654      /**
5655       * Calls parent::__construct with specific arguments
5656       */
5657      public function __construct() {
5658          parent::__construct('gradebookroles', get_string('gradebookroles', 'admin'),
5659              get_string('configgradebookroles', 'admin'),
5660              array('student'));
5661      }
5662  }
5663  
5664  
5665  /**
5666   *
5667   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5668   */
5669  class admin_setting_regradingcheckbox extends admin_setting_configcheckbox {
5670      /**
5671       * Saves the new settings passed in $data
5672       *
5673       * @param string $data
5674       * @return mixed string or Array
5675       */
5676      public function write_setting($data) {
5677          global $CFG, $DB;
5678  
5679          $oldvalue  = $this->config_read($this->name);
5680          $return    = parent::write_setting($data);
5681          $newvalue  = $this->config_read($this->name);
5682  
5683          if ($oldvalue !== $newvalue) {
5684          // force full regrading
5685              $DB->set_field('grade_items', 'needsupdate', 1, array('needsupdate'=>0));
5686          }
5687  
5688          return $return;
5689      }
5690  }
5691  
5692  
5693  /**
5694   * Which roles to show on course description page
5695   *
5696   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5697   */
5698  class admin_setting_special_coursecontact extends admin_setting_pickroles {
5699      /**
5700       * Calls parent::__construct with specific arguments
5701       */
5702      public function __construct() {
5703          parent::__construct('coursecontact', get_string('coursecontact', 'admin'),
5704              get_string('coursecontact_desc', 'admin'),
5705              array('editingteacher'));
5706          $this->set_updatedcallback(function (){
5707              cache::make('core', 'coursecontacts')->purge();
5708          });
5709      }
5710  }
5711  
5712  
5713  /**
5714   *
5715   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5716   */
5717  class admin_setting_special_gradelimiting extends admin_setting_configcheckbox {
5718      /**
5719       * Calls parent::__construct with specific arguments
5720       */
5721      public function __construct() {
5722          parent::__construct('unlimitedgrades', get_string('unlimitedgrades', 'grades'),
5723              get_string('unlimitedgrades_help', 'grades'), '0', '1', '0');
5724      }
5725  
5726      /**
5727       * Old syntax of class constructor. Deprecated in PHP7.
5728       *
5729       * @deprecated since Moodle 3.1
5730       */
5731      public function admin_setting_special_gradelimiting() {
5732          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
5733          self::__construct();
5734      }
5735  
5736      /**
5737       * Force site regrading
5738       */
5739      function regrade_all() {
5740          global $CFG;
5741          require_once("$CFG->libdir/gradelib.php");
5742          grade_force_site_regrading();
5743      }
5744  
5745      /**
5746       * Saves the new settings
5747       *
5748       * @param mixed $data
5749       * @return string empty string or error message
5750       */
5751      function write_setting($data) {
5752          $previous = $this->get_setting();
5753  
5754          if ($previous === null) {
5755              if ($data) {
5756                  $this->regrade_all();
5757              }
5758          } else {
5759              if ($data != $previous) {
5760                  $this->regrade_all();
5761              }
5762          }
5763          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
5764      }
5765  
5766  }
5767  
5768  /**
5769   * Special setting for $CFG->grade_minmaxtouse.
5770   *
5771   * @package    core
5772   * @copyright  2015 Frédéric Massart - FMCorz.net
5773   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5774   */
5775  class admin_setting_special_grademinmaxtouse extends admin_setting_configselect {
5776  
5777      /**
5778       * Constructor.
5779       */
5780      public function __construct() {
5781          parent::__construct('grade_minmaxtouse', new lang_string('minmaxtouse', 'grades'),
5782              new lang_string('minmaxtouse_desc', 'grades'), GRADE_MIN_MAX_FROM_GRADE_ITEM,
5783              array(
5784                  GRADE_MIN_MAX_FROM_GRADE_ITEM => get_string('gradeitemminmax', 'grades'),
5785                  GRADE_MIN_MAX_FROM_GRADE_GRADE => get_string('gradegrademinmax', 'grades')
5786              )
5787          );
5788      }
5789  
5790      /**
5791       * Saves the new setting.
5792       *
5793       * @param mixed $data
5794       * @return string empty string or error message
5795       */
5796      function write_setting($data) {
5797          global $CFG;
5798  
5799          $previous = $this->get_setting();
5800          $result = parent::write_setting($data);
5801  
5802          // If saved and the value has changed.
5803          if (empty($result) && $previous != $data) {
5804              require_once($CFG->libdir . '/gradelib.php');
5805              grade_force_site_regrading();
5806          }
5807  
5808          return $result;
5809      }
5810  
5811  }
5812  
5813  
5814  /**
5815   * Primary grade export plugin - has state tracking.
5816   *
5817   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5818   */
5819  class admin_setting_special_gradeexport extends admin_setting_configmulticheckbox {
5820      /**
5821       * Calls parent::__construct with specific arguments
5822       */
5823      public function __construct() {
5824          parent::__construct('gradeexport', get_string('gradeexport', 'admin'),
5825              get_string('configgradeexport', 'admin'), array(), NULL);
5826      }
5827  
5828      /**
5829       * Load the available choices for the multicheckbox
5830       *
5831       * @return bool always returns true
5832       */
5833      public function load_choices() {
5834          if (is_array($this->choices)) {
5835              return true;
5836          }
5837          $this->choices = array();
5838  
5839          if ($plugins = core_component::get_plugin_list('gradeexport')) {
5840              foreach($plugins as $plugin => $unused) {
5841                  $this->choices[$plugin] = get_string('pluginname', 'gradeexport_'.$plugin);
5842              }
5843          }
5844          return true;
5845      }
5846  }
5847  
5848  
5849  /**
5850   * A setting for setting the default grade point value. Must be an integer between 1 and $CFG->gradepointmax.
5851   *
5852   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5853   */
5854  class admin_setting_special_gradepointdefault extends admin_setting_configtext {
5855      /**
5856       * Config gradepointmax constructor
5857       *
5858       * @param string $name Overidden by "gradepointmax"
5859       * @param string $visiblename Overridden by "gradepointmax" language string.
5860       * @param string $description Overridden by "gradepointmax_help" language string.
5861       * @param string $defaultsetting Not used, overridden by 100.
5862       * @param mixed $paramtype Overridden by PARAM_INT.
5863       * @param int $size Overridden by 5.
5864       */
5865      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
5866          $name = 'gradepointdefault';
5867          $visiblename = get_string('gradepointdefault', 'grades');
5868          $description = get_string('gradepointdefault_help', 'grades');
5869          $defaultsetting = 100;
5870          $paramtype = PARAM_INT;
5871          $size = 5;
5872          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
5873      }
5874  
5875      /**
5876       * Validate data before storage
5877       * @param string $data The submitted data
5878       * @return bool|string true if ok, string if error found
5879       */
5880      public function validate($data) {
5881          global $CFG;
5882          if (((string)(int)$data === (string)$data && $data > 0 && $data <= $CFG->gradepointmax)) {
5883              return true;
5884          } else {
5885              return get_string('gradepointdefault_validateerror', 'grades');
5886          }
5887      }
5888  }
5889  
5890  
5891  /**
5892   * A setting for setting the maximum grade value. Must be an integer between 1 and 10000.
5893   *
5894   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5895   */
5896  class admin_setting_special_gradepointmax extends admin_setting_configtext {
5897  
5898      /**
5899       * Config gradepointmax constructor
5900       *
5901       * @param string $name Overidden by "gradepointmax"
5902       * @param string $visiblename Overridden by "gradepointmax" language string.
5903       * @param string $description Overridden by "gradepointmax_help" language string.
5904       * @param string $defaultsetting Not used, overridden by 100.
5905       * @param mixed $paramtype Overridden by PARAM_INT.
5906       * @param int $size Overridden by 5.
5907       */
5908      public function __construct($name = '', $visiblename = '', $description = '', $defaultsetting = '', $paramtype = PARAM_INT, $size = 5) {
5909          $name = 'gradepointmax';
5910          $visiblename = get_string('gradepointmax', 'grades');
5911          $description = get_string('gradepointmax_help', 'grades');
5912          $defaultsetting = 100;
5913          $paramtype = PARAM_INT;
5914          $size = 5;
5915          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
5916      }
5917  
5918      /**
5919       * Save the selected setting
5920       *
5921       * @param string $data The selected site
5922       * @return string empty string or error message
5923       */
5924      public function write_setting($data) {
5925          if ($data === '') {
5926              $data = (int)$this->defaultsetting;
5927          } else {
5928              $data = $data;
5929          }
5930          return parent::write_setting($data);
5931      }
5932  
5933      /**
5934       * Validate data before storage
5935       * @param string $data The submitted data
5936       * @return bool|string true if ok, string if error found
5937       */
5938      public function validate($data) {
5939          if (((string)(int)$data === (string)$data && $data > 0 && $data <= 10000)) {
5940              return true;
5941          } else {
5942              return get_string('gradepointmax_validateerror', 'grades');
5943          }
5944      }
5945  
5946      /**
5947       * Return an XHTML string for the setting
5948       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
5949       * @param string $query search query to be highlighted
5950       * @return string XHTML to display control
5951       */
5952      public function output_html($data, $query = '') {
5953          global $OUTPUT;
5954  
5955          $default = $this->get_defaultsetting();
5956          $context = (object) [
5957              'size' => $this->size,
5958              'id' => $this->get_id(),
5959              'name' => $this->get_full_name(),
5960              'value' => $data,
5961              'attributes' => [
5962                  'maxlength' => 5
5963              ],
5964              'forceltr' => $this->get_force_ltr()
5965          ];
5966          $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
5967  
5968          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
5969      }
5970  }
5971  
5972  
5973  /**
5974   * Grade category settings
5975   *
5976   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5977   */
5978  class admin_setting_gradecat_combo extends admin_setting {
5979      /** @var array Array of choices */
5980      public $choices;
5981  
5982      /**
5983       * Sets choices and calls parent::__construct with passed arguments
5984       * @param string $name
5985       * @param string $visiblename
5986       * @param string $description
5987       * @param mixed $defaultsetting string or array depending on implementation
5988       * @param array $choices An array of choices for the control
5989       */
5990      public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
5991          $this->choices = $choices;
5992          parent::__construct($name, $visiblename, $description, $defaultsetting);
5993      }
5994  
5995      /**
5996       * Return the current setting(s) array
5997       *
5998       * @return array Array of value=>xx, forced=>xx, adv=>xx
5999       */
6000      public function get_setting() {
6001          global $CFG;
6002  
6003          $value = $this->config_read($this->name);
6004          $flag  = $this->config_read($this->name.'_flag');
6005  
6006          if (is_null($value) or is_null($flag)) {
6007              return NULL;
6008          }
6009  
6010          $flag   = (int)$flag;
6011          $forced = (boolean)(1 & $flag); // first bit
6012          $adv    = (boolean)(2 & $flag); // second bit
6013  
6014          return array('value' => $value, 'forced' => $forced, 'adv' => $adv);
6015      }
6016  
6017      /**
6018       * Save the new settings passed in $data
6019       *
6020       * @todo Add vartype handling to ensure $data is array
6021       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6022       * @return string empty or error message
6023       */
6024      public function write_setting($data) {
6025          global $CFG;
6026  
6027          $value  = $data['value'];
6028          $forced = empty($data['forced']) ? 0 : 1;
6029          $adv    = empty($data['adv'])    ? 0 : 2;
6030          $flag   = ($forced | $adv); //bitwise or
6031  
6032          if (!in_array($value, array_keys($this->choices))) {
6033              return 'Error setting ';
6034          }
6035  
6036          $oldvalue  = $this->config_read($this->name);
6037          $oldflag   = (int)$this->config_read($this->name.'_flag');
6038          $oldforced = (1 & $oldflag); // first bit
6039  
6040          $result1 = $this->config_write($this->name, $value);
6041          $result2 = $this->config_write($this->name.'_flag', $flag);
6042  
6043          // force regrade if needed
6044          if ($oldforced != $forced or ($forced and $value != $oldvalue)) {
6045              require_once($CFG->libdir.'/gradelib.php');
6046              grade_category::updated_forced_settings();
6047          }
6048  
6049          if ($result1 and $result2) {
6050              return '';
6051          } else {
6052              return get_string('errorsetting', 'admin');
6053          }
6054      }
6055  
6056      /**
6057       * Return XHTML to display the field and wrapping div
6058       *
6059       * @todo Add vartype handling to ensure $data is array
6060       * @param array $data Associative array of value=>xx, forced=>xx, adv=>xx
6061       * @param string $query
6062       * @return string XHTML to display control
6063       */
6064      public function output_html($data, $query='') {
6065          global $OUTPUT;
6066  
6067          $value  = $data['value'];
6068  
6069          $default = $this->get_defaultsetting();
6070          if (!is_null($default)) {
6071              $defaultinfo = array();
6072              if (isset($this->choices[$default['value']])) {
6073                  $defaultinfo[] = $this->choices[$default['value']];
6074              }
6075              if (!empty($default['forced'])) {
6076                  $defaultinfo[] = get_string('force');
6077              }
6078              if (!empty($default['adv'])) {
6079                  $defaultinfo[] = get_string('advanced');
6080              }
6081              $defaultinfo = implode(', ', $defaultinfo);
6082  
6083          } else {
6084              $defaultinfo = NULL;
6085          }
6086  
6087          $options = $this->choices;
6088          $context = (object) [
6089              'id' => $this->get_id(),
6090              'name' => $this->get_full_name(),
6091              'forced' => !empty($data['forced']),
6092              'advanced' => !empty($data['adv']),
6093              'options' => array_map(function($option) use ($options, $value) {
6094                  return [
6095                      'value' => $option,
6096                      'name' => $options[$option],
6097                      'selected' => $option == $value
6098                  ];
6099              }, array_keys($options)),
6100          ];
6101  
6102          $element = $OUTPUT->render_from_template('core_admin/setting_gradecat_combo', $context);
6103  
6104          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
6105      }
6106  }
6107  
6108  
6109  /**
6110   * Selection of grade report in user profiles
6111   *
6112   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6113   */
6114  class admin_setting_grade_profilereport extends admin_setting_configselect {
6115      /**
6116       * Calls parent::__construct with specific arguments
6117       */
6118      public function __construct() {
6119          parent::__construct('grade_profilereport', get_string('profilereport', 'grades'), get_string('profilereport_help', 'grades'), 'user', null);
6120      }
6121  
6122      /**
6123       * Loads an array of choices for the configselect control
6124       *
6125       * @return bool always return true
6126       */
6127      public function load_choices() {
6128          if (is_array($this->choices)) {
6129              return true;
6130          }
6131          $this->choices = array();
6132  
6133          global $CFG;
6134          require_once($CFG->libdir.'/gradelib.php');
6135  
6136          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6137              if (file_exists($plugindir.'/lib.php')) {
6138                  require_once($plugindir.'/lib.php');
6139                  $functionname = 'grade_report_'.$plugin.'_profilereport';
6140                  if (function_exists($functionname)) {
6141                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_'.$plugin);
6142                  }
6143              }
6144          }
6145          return true;
6146      }
6147  }
6148  
6149  /**
6150   * Provides a selection of grade reports to be used for "grades".
6151   *
6152   * @copyright 2015 Adrian Greeve <adrian@moodle.com>
6153   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6154   */
6155  class admin_setting_my_grades_report extends admin_setting_configselect {
6156  
6157      /**
6158       * Calls parent::__construct with specific arguments.
6159       */
6160      public function __construct() {
6161          parent::__construct('grade_mygrades_report', new lang_string('mygrades', 'grades'),
6162                  new lang_string('mygrades_desc', 'grades'), 'overview', null);
6163      }
6164  
6165      /**
6166       * Loads an array of choices for the configselect control.
6167       *
6168       * @return bool always returns true.
6169       */
6170      public function load_choices() {
6171          global $CFG; // Remove this line and behold the horror of behat test failures!
6172          $this->choices = array();
6173          foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
6174              if (file_exists($plugindir . '/lib.php')) {
6175                  require_once($plugindir . '/lib.php');
6176                  // Check to see if the class exists. Check the correct plugin convention first.
6177                  if (class_exists('gradereport_' . $plugin)) {
6178                      $classname = 'gradereport_' . $plugin;
6179                  } else if (class_exists('grade_report_' . $plugin)) {
6180                      // We are using the old plugin naming convention.
6181                      $classname = 'grade_report_' . $plugin;
6182                  } else {
6183                      continue;
6184                  }
6185                  if ($classname::supports_mygrades()) {
6186                      $this->choices[$plugin] = get_string('pluginname', 'gradereport_' . $plugin);
6187                  }
6188              }
6189          }
6190          // Add an option to specify an external url.
6191          $this->choices['external'] = get_string('externalurl', 'grades');
6192          return true;
6193      }
6194  }
6195  
6196  /**
6197   * Special class for register auth selection
6198   *
6199   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6200   */
6201  class admin_setting_special_registerauth extends admin_setting_configselect {
6202      /**
6203       * Calls parent::__construct with specific arguments
6204       */
6205      public function __construct() {
6206          parent::__construct('registerauth', get_string('selfregistration', 'auth'), get_string('selfregistration_help', 'auth'), '', null);
6207      }
6208  
6209      /**
6210       * Returns the default option
6211       *
6212       * @return string empty or default option
6213       */
6214      public function get_defaultsetting() {
6215          $this->load_choices();
6216          $defaultsetting = parent::get_defaultsetting();
6217          if (array_key_exists($defaultsetting, $this->choices)) {
6218              return $defaultsetting;
6219          } else {
6220              return '';
6221          }
6222      }
6223  
6224      /**
6225       * Loads the possible choices for the array
6226       *
6227       * @return bool always returns true
6228       */
6229      public function load_choices() {
6230          global $CFG;
6231  
6232          if (is_array($this->choices)) {
6233              return true;
6234          }
6235          $this->choices = array();
6236          $this->choices[''] = get_string('disable');
6237  
6238          $authsenabled = get_enabled_auth_plugins();
6239  
6240          foreach ($authsenabled as $auth) {
6241              $authplugin = get_auth_plugin($auth);
6242              if (!$authplugin->can_signup()) {
6243                  continue;
6244              }
6245              // Get the auth title (from core or own auth lang files)
6246              $authtitle = $authplugin->get_title();
6247              $this->choices[$auth] = $authtitle;
6248          }
6249          return true;
6250      }
6251  }
6252  
6253  
6254  /**
6255   * General plugins manager
6256   */
6257  class admin_page_pluginsoverview extends admin_externalpage {
6258  
6259      /**
6260       * Sets basic information about the external page
6261       */
6262      public function __construct() {
6263          global $CFG;
6264          parent::__construct('pluginsoverview', get_string('pluginsoverview', 'core_admin'),
6265              "$CFG->wwwroot/$CFG->admin/plugins.php");
6266      }
6267  }
6268  
6269  /**
6270   * Module manage page
6271   *
6272   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6273   */
6274  class admin_page_managemods extends admin_externalpage {
6275      /**
6276       * Calls parent::__construct with specific arguments
6277       */
6278      public function __construct() {
6279          global $CFG;
6280          parent::__construct('managemodules', get_string('modsettings', 'admin'), "$CFG->wwwroot/$CFG->admin/modules.php");
6281      }
6282  
6283      /**
6284       * Try to find the specified module
6285       *
6286       * @param string $query The module to search for
6287       * @return array
6288       */
6289      public function search($query) {
6290          global $CFG, $DB;
6291          if ($result = parent::search($query)) {
6292              return $result;
6293          }
6294  
6295          $found = false;
6296          if ($modules = $DB->get_records('modules')) {
6297              foreach ($modules as $module) {
6298                  if (!file_exists("$CFG->dirroot/mod/$module->name/lib.php")) {
6299                      continue;
6300                  }
6301                  if (strpos($module->name, $query) !== false) {
6302                      $found = true;
6303                      break;
6304                  }
6305                  $strmodulename = get_string('modulename', $module->name);
6306                  if (strpos(core_text::strtolower($strmodulename), $query) !== false) {
6307                      $found = true;
6308                      break;
6309                  }
6310              }
6311          }
6312          if ($found) {
6313              $result = new stdClass();
6314              $result->page     = $this;
6315              $result->settings = array();
6316              return array($this->name => $result);
6317          } else {
6318              return array();
6319          }
6320      }
6321  }
6322  
6323  
6324  /**
6325   * Special class for enrol plugins management.
6326   *
6327   * @copyright 2010 Petr Skoda {@link http://skodak.org}
6328   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6329   */
6330  class admin_setting_manageenrols extends admin_setting {
6331      /**
6332       * Calls parent::__construct with specific arguments
6333       */
6334      public function __construct() {
6335          $this->nosave = true;
6336          parent::__construct('enrolsui', get_string('manageenrols', 'enrol'), '', '');
6337      }
6338  
6339      /**
6340       * Always returns true, does nothing
6341       *
6342       * @return true
6343       */
6344      public function get_setting() {
6345          return true;
6346      }
6347  
6348      /**
6349       * Always returns true, does nothing
6350       *
6351       * @return true
6352       */
6353      public function get_defaultsetting() {
6354          return true;
6355      }
6356  
6357      /**
6358       * Always returns '', does not write anything
6359       *
6360       * @return string Always returns ''
6361       */
6362      public function write_setting($data) {
6363      // do not write any setting
6364          return '';
6365      }
6366  
6367      /**
6368       * Checks if $query is one of the available enrol plugins
6369       *
6370       * @param string $query The string to search for
6371       * @return bool Returns true if found, false if not
6372       */
6373      public function is_related($query) {
6374          if (parent::is_related($query)) {
6375              return true;
6376          }
6377  
6378          $query = core_text::strtolower($query);
6379          $enrols = enrol_get_plugins(false);
6380          foreach ($enrols as $name=>$enrol) {
6381              $localised = get_string('pluginname', 'enrol_'.$name);
6382              if (strpos(core_text::strtolower($name), $query) !== false) {
6383                  return true;
6384              }
6385              if (strpos(core_text::strtolower($localised), $query) !== false) {
6386                  return true;
6387              }
6388          }
6389          return false;
6390      }
6391  
6392      /**
6393       * Builds the XHTML to display the control
6394       *
6395       * @param string $data Unused
6396       * @param string $query
6397       * @return string
6398       */
6399      public function output_html($data, $query='') {
6400          global $CFG, $OUTPUT, $DB, $PAGE;
6401  
6402          // Display strings.
6403          $strup        = get_string('up');
6404          $strdown      = get_string('down');
6405          $strsettings  = get_string('settings');
6406          $strenable    = get_string('enable');
6407          $strdisable   = get_string('disable');
6408          $struninstall = get_string('uninstallplugin', 'core_admin');
6409          $strusage     = get_string('enrolusage', 'enrol');
6410          $strversion   = get_string('version');
6411          $strtest      = get_string('testsettings', 'core_enrol');
6412  
6413          $pluginmanager = core_plugin_manager::instance();
6414  
6415          $enrols_available = enrol_get_plugins(false);
6416          $active_enrols    = enrol_get_plugins(true);
6417  
6418          $allenrols = array();
6419          foreach ($active_enrols as $key=>$enrol) {
6420              $allenrols[$key] = true;
6421          }
6422          foreach ($enrols_available as $key=>$enrol) {
6423              $allenrols[$key] = true;
6424          }
6425          // Now find all borked plugins and at least allow then to uninstall.
6426          $condidates = $DB->get_fieldset_sql("SELECT DISTINCT enrol FROM {enrol}");
6427          foreach ($condidates as $candidate) {
6428              if (empty($allenrols[$candidate])) {
6429                  $allenrols[$candidate] = true;
6430              }
6431          }
6432  
6433          $return = $OUTPUT->heading(get_string('actenrolshhdr', 'enrol'), 3, 'main', true);
6434          $return .= $OUTPUT->box_start('generalbox enrolsui');
6435  
6436          $table = new html_table();
6437          $table->head  = array(get_string('name'), $strusage, $strversion, $strenable, $strup.'/'.$strdown, $strsettings, $strtest, $struninstall);
6438          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
6439          $table->id = 'courseenrolmentplugins';
6440          $table->attributes['class'] = 'admintable generaltable';
6441          $table->data  = array();
6442  
6443          // Iterate through enrol plugins and add to the display table.
6444          $updowncount = 1;
6445          $enrolcount = count($active_enrols);
6446          $url = new moodle_url('/admin/enrol.php', array('sesskey'=>sesskey()));
6447          $printed = array();
6448          foreach($allenrols as $enrol => $unused) {
6449              $plugininfo = $pluginmanager->get_plugin_info('enrol_'.$enrol);
6450              $version = get_config('enrol_'.$enrol, 'version');
6451              if ($version === false) {
6452                  $version = '';
6453              }
6454  
6455              if (get_string_manager()->string_exists('pluginname', 'enrol_'.$enrol)) {
6456                  $name = get_string('pluginname', 'enrol_'.$enrol);
6457              } else {
6458                  $name = $enrol;
6459              }
6460              // Usage.
6461              $ci = $DB->count_records('enrol', array('enrol'=>$enrol));
6462              $cp = $DB->count_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($enrol));
6463              $usage = "$ci / $cp";
6464  
6465              // Hide/show links.
6466              $class = '';
6467              if (isset($active_enrols[$enrol])) {
6468                  $aurl = new moodle_url($url, array('action'=>'disable', 'enrol'=>$enrol));
6469                  $hideshow = "<a href=\"$aurl\">";
6470                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
6471                  $enabled = true;
6472                  $displayname = $name;
6473              } else if (isset($enrols_available[$enrol])) {
6474                  $aurl = new moodle_url($url, array('action'=>'enable', 'enrol'=>$enrol));
6475                  $hideshow = "<a href=\"$aurl\">";
6476                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
6477                  $enabled = false;
6478                  $displayname = $name;
6479                  $class = 'dimmed_text';
6480              } else {
6481                  $hideshow = '';
6482                  $enabled = false;
6483                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
6484              }
6485              if ($PAGE->theme->resolve_image_location('icon', 'enrol_' . $name, false)) {
6486                  $icon = $OUTPUT->pix_icon('icon', '', 'enrol_' . $name, array('class' => 'icon pluginicon'));
6487              } else {
6488                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
6489              }
6490  
6491              // Up/down link (only if enrol is enabled).
6492              $updown = '';
6493              if ($enabled) {
6494                  if ($updowncount > 1) {
6495                      $aurl = new moodle_url($url, array('action'=>'up', 'enrol'=>$enrol));
6496                      $updown .= "<a href=\"$aurl\">";
6497                      $updown .= $OUTPUT->pix_icon('t/up', $strup) . '</a>&nbsp;';
6498                  } else {
6499                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6500                  }
6501                  if ($updowncount < $enrolcount) {
6502                      $aurl = new moodle_url($url, array('action'=>'down', 'enrol'=>$enrol));
6503                      $updown .= "<a href=\"$aurl\">";
6504                      $updown .= $OUTPUT->pix_icon('t/down', $strdown) . '</a>&nbsp;';
6505                  } else {
6506                      $updown .= $OUTPUT->spacer() . '&nbsp;';
6507                  }
6508                  ++$updowncount;
6509              }
6510  
6511              // Add settings link.
6512              if (!$version) {
6513                  $settings = '';
6514              } else if ($surl = $plugininfo->get_settings_url()) {
6515                  $settings = html_writer::link($surl, $strsettings);
6516              } else {
6517                  $settings = '';
6518              }
6519  
6520              // Add uninstall info.
6521              $uninstall = '';
6522              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('enrol_'.$enrol, 'manage')) {
6523                  $uninstall = html_writer::link($uninstallurl, $struninstall);
6524              }
6525  
6526              $test = '';
6527              if (!empty($enrols_available[$enrol]) and method_exists($enrols_available[$enrol], 'test_settings')) {
6528                  $testsettingsurl = new moodle_url('/enrol/test_settings.php', array('enrol'=>$enrol, 'sesskey'=>sesskey()));
6529                  $test = html_writer::link($testsettingsurl, $strtest);
6530              }
6531  
6532              // Add a row to the table.
6533              $row = new html_table_row(array($icon.$displayname, $usage, $version, $hideshow, $updown, $settings, $test, $uninstall));
6534              if ($class) {
6535                  $row->attributes['class'] = $class;
6536              }
6537              $table->data[] = $row;
6538  
6539              $printed[$enrol] = true;
6540          }
6541  
6542          $return .= html_writer::table($table);
6543          $return .= get_string('configenrolplugins', 'enrol').'<br />'.get_string('tablenosave', 'admin');
6544          $return .= $OUTPUT->box_end();
6545          return highlight($query, $return);
6546      }
6547  }
6548  
6549  
6550  /**
6551   * Blocks manage page
6552   *
6553   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6554   */
6555  class admin_page_manageblocks extends admin_externalpage {
6556      /**
6557       * Calls parent::__construct with specific arguments
6558       */
6559      public function __construct() {
6560          global $CFG;
6561          parent::__construct('manageblocks', get_string('blocksettings', 'admin'), "$CFG->wwwroot/$CFG->admin/blocks.php");
6562      }
6563  
6564      /**
6565       * Search for a specific block
6566       *
6567       * @param string $query The string to search for
6568       * @return array
6569       */
6570      public function search($query) {
6571          global $CFG, $DB;
6572          if ($result = parent::search($query)) {
6573              return $result;
6574          }
6575  
6576          $found = false;
6577          if ($blocks = $DB->get_records('block')) {
6578              foreach ($blocks as $block) {
6579                  if (!file_exists("$CFG->dirroot/blocks/$block->name/")) {
6580                      continue;
6581                  }
6582                  if (strpos($block->name, $query) !== false) {
6583                      $found = true;
6584                      break;
6585                  }
6586                  $strblockname = get_string('pluginname', 'block_'.$block->name);
6587                  if (strpos(core_text::strtolower($strblockname), $query) !== false) {
6588                      $found = true;
6589                      break;
6590                  }
6591              }
6592          }
6593          if ($found) {
6594              $result = new stdClass();
6595              $result->page     = $this;
6596              $result->settings = array();
6597              return array($this->name => $result);
6598          } else {
6599              return array();
6600          }
6601      }
6602  }
6603  
6604  /**
6605   * Message outputs configuration
6606   *
6607   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6608   */
6609  class admin_page_managemessageoutputs extends admin_externalpage {
6610      /**
6611       * Calls parent::__construct with specific arguments
6612       */
6613      public function __construct() {
6614          global $CFG;
6615          parent::__construct('managemessageoutputs',
6616              get_string('defaultmessageoutputs', 'message'),
6617              new moodle_url('/admin/message.php')
6618          );
6619      }
6620  
6621      /**
6622       * Search for a specific message processor
6623       *
6624       * @param string $query The string to search for
6625       * @return array
6626       */
6627      public function search($query) {
6628          global $CFG, $DB;
6629          if ($result = parent::search($query)) {
6630              return $result;
6631          }
6632  
6633          $found = false;
6634          if ($processors = get_message_processors()) {
6635              foreach ($processors as $processor) {
6636                  if (!$processor->available) {
6637                      continue;
6638                  }
6639                  if (strpos($processor->name, $query) !== false) {
6640                      $found = true;
6641                      break;
6642                  }
6643                  $strprocessorname = get_string('pluginname', 'message_'.$processor->name);
6644                  if (strpos(core_text::strtolower($strprocessorname), $query) !== false) {
6645                      $found = true;
6646                      break;
6647                  }
6648              }
6649          }
6650          if ($found) {
6651              $result = new stdClass();
6652              $result->page     = $this;
6653              $result->settings = array();
6654              return array($this->name => $result);
6655          } else {
6656              return array();
6657          }
6658      }
6659  }
6660  
6661  /**
6662   * Default message outputs configuration
6663   *
6664   * @deprecated since Moodle 3.7 MDL-64495. Please use admin_page_managemessageoutputs instead.
6665   * @todo       MDL-64866 This will be deleted in Moodle 3.11.
6666   *
6667   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6668   */
6669  class admin_page_defaultmessageoutputs extends admin_page_managemessageoutputs {
6670      /**
6671       * Calls parent::__construct with specific arguments
6672       *
6673       * @deprecated since Moodle 3.7 MDL-64495. Please use admin_page_managemessageoutputs instead.
6674       * @todo       MDL-64866 This will be deleted in Moodle 3.11.
6675       */
6676      public function __construct() {
6677          global $CFG;
6678  
6679          debugging('admin_page_defaultmessageoutputs class is deprecated. Please use admin_page_managemessageoutputs instead.',
6680              DEBUG_DEVELOPER);
6681  
6682          admin_externalpage::__construct('defaultmessageoutputs', get_string('defaultmessageoutputs', 'message'), new moodle_url('/message/defaultoutputs.php'));
6683      }
6684  }
6685  
6686  
6687  /**
6688   * Manage question behaviours page
6689   *
6690   * @copyright  2011 The Open University
6691   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6692   */
6693  class admin_page_manageqbehaviours extends admin_externalpage {
6694      /**
6695       * Constructor
6696       */
6697      public function __construct() {
6698          global $CFG;
6699          parent::__construct('manageqbehaviours', get_string('manageqbehaviours', 'admin'),
6700                  new moodle_url('/admin/qbehaviours.php'));
6701      }
6702  
6703      /**
6704       * Search question behaviours for the specified string
6705       *
6706       * @param string $query The string to search for in question behaviours
6707       * @return array
6708       */
6709      public function search($query) {
6710          global $CFG;
6711          if ($result = parent::search($query)) {
6712              return $result;
6713          }
6714  
6715          $found = false;
6716          require_once($CFG->dirroot . '/question/engine/lib.php');
6717          foreach (core_component::get_plugin_list('qbehaviour') as $behaviour => $notused) {
6718              if (strpos(core_text::strtolower(question_engine::get_behaviour_name($behaviour)),
6719                      $query) !== false) {
6720                  $found = true;
6721                  break;
6722              }
6723          }
6724          if ($found) {
6725              $result = new stdClass();
6726              $result->page     = $this;
6727              $result->settings = array();
6728              return array($this->name => $result);
6729          } else {
6730              return array();
6731          }
6732      }
6733  }
6734  
6735  
6736  /**
6737   * Question type manage page
6738   *
6739   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6740   */
6741  class admin_page_manageqtypes extends admin_externalpage {
6742      /**
6743       * Calls parent::__construct with specific arguments
6744       */
6745      public function __construct() {
6746          global $CFG;
6747          parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'),
6748                  new moodle_url('/admin/qtypes.php'));
6749      }
6750  
6751      /**
6752       * Search question types for the specified string
6753       *
6754       * @param string $query The string to search for in question types
6755       * @return array
6756       */
6757      public function search($query) {
6758          global $CFG;
6759          if ($result = parent::search($query)) {
6760              return $result;
6761          }
6762  
6763          $found = false;
6764          require_once($CFG->dirroot . '/question/engine/bank.php');
6765          foreach (question_bank::get_all_qtypes() as $qtype) {
6766              if (strpos(core_text::strtolower($qtype->local_name()), $query) !== false) {
6767                  $found = true;
6768                  break;
6769              }
6770          }
6771          if ($found) {
6772              $result = new stdClass();
6773              $result->page     = $this;
6774              $result->settings = array();
6775              return array($this->name => $result);
6776          } else {
6777              return array();
6778          }
6779      }
6780  }
6781  
6782  
6783  class admin_page_manageportfolios extends admin_externalpage {
6784      /**
6785       * Calls parent::__construct with specific arguments
6786       */
6787      public function __construct() {
6788          global $CFG;
6789          parent::__construct('manageportfolios', get_string('manageportfolios', 'portfolio'),
6790                  "$CFG->wwwroot/$CFG->admin/portfolio.php");
6791      }
6792  
6793      /**
6794       * Searches page for the specified string.
6795       * @param string $query The string to search for
6796       * @return bool True if it is found on this page
6797       */
6798      public function search($query) {
6799          global $CFG;
6800          if ($result = parent::search($query)) {
6801              return $result;
6802          }
6803  
6804          $found = false;
6805          $portfolios = core_component::get_plugin_list('portfolio');
6806          foreach ($portfolios as $p => $dir) {
6807              if (strpos($p, $query) !== false) {
6808                  $found = true;
6809                  break;
6810              }
6811          }
6812          if (!$found) {
6813              foreach (portfolio_instances(false, false) as $instance) {
6814                  $title = $instance->get('name');
6815                  if (strpos(core_text::strtolower($title), $query) !== false) {
6816                      $found = true;
6817                      break;
6818                  }
6819              }
6820          }
6821  
6822          if ($found) {
6823              $result = new stdClass();
6824              $result->page     = $this;
6825              $result->settings = array();
6826              return array($this->name => $result);
6827          } else {
6828              return array();
6829          }
6830      }
6831  }
6832  
6833  
6834  class admin_page_managerepositories extends admin_externalpage {
6835      /**
6836       * Calls parent::__construct with specific arguments
6837       */
6838      public function __construct() {
6839          global $CFG;
6840          parent::__construct('managerepositories', get_string('manage',
6841                  'repository'), "$CFG->wwwroot/$CFG->admin/repository.php");
6842      }
6843  
6844      /**
6845       * Searches page for the specified string.
6846       * @param string $query The string to search for
6847       * @return bool True if it is found on this page
6848       */
6849      public function search($query) {
6850          global $CFG;
6851          if ($result = parent::search($query)) {
6852              return $result;
6853          }
6854  
6855          $found = false;
6856          $repositories= core_component::get_plugin_list('repository');
6857          foreach ($repositories as $p => $dir) {
6858              if (strpos($p, $query) !== false) {
6859                  $found = true;
6860                  break;
6861              }
6862          }
6863          if (!$found) {
6864              foreach (repository::get_types() as $instance) {
6865                  $title = $instance->get_typename();
6866                  if (strpos(core_text::strtolower($title), $query) !== false) {
6867                      $found = true;
6868                      break;
6869                  }
6870              }
6871          }
6872  
6873          if ($found) {
6874              $result = new stdClass();
6875              $result->page     = $this;
6876              $result->settings = array();
6877              return array($this->name => $result);
6878          } else {
6879              return array();
6880          }
6881      }
6882  }
6883  
6884  
6885  /**
6886   * Special class for authentication administration.
6887   *
6888   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6889   */
6890  class admin_setting_manageauths extends admin_setting {
6891      /**
6892       * Calls parent::__construct with specific arguments
6893       */
6894      public function __construct() {
6895          $this->nosave = true;
6896          parent::__construct('authsui', get_string('authsettings', 'admin'), '', '');
6897      }
6898  
6899      /**
6900       * Always returns true
6901       *
6902       * @return true
6903       */
6904      public function get_setting() {
6905          return true;
6906      }
6907  
6908      /**
6909       * Always returns true
6910       *
6911       * @return true
6912       */
6913      public function get_defaultsetting() {
6914          return true;
6915      }
6916  
6917      /**
6918       * Always returns '' and doesn't write anything
6919       *
6920       * @return string Always returns ''
6921       */
6922      public function write_setting($data) {
6923      // do not write any setting
6924          return '';
6925      }
6926  
6927      /**
6928       * Search to find if Query is related to auth plugin
6929       *
6930       * @param string $query The string to search for
6931       * @return bool true for related false for not
6932       */
6933      public function is_related($query) {
6934          if (parent::is_related($query)) {
6935              return true;
6936          }
6937  
6938          $authsavailable = core_component::get_plugin_list('auth');
6939          foreach ($authsavailable as $auth => $dir) {
6940              if (strpos($auth, $query) !== false) {
6941                  return true;
6942              }
6943              $authplugin = get_auth_plugin($auth);
6944              $authtitle = $authplugin->get_title();
6945              if (strpos(core_text::strtolower($authtitle), $query) !== false) {
6946                  return true;
6947              }
6948          }
6949          return false;
6950      }
6951  
6952      /**
6953       * Return XHTML to display control
6954       *
6955       * @param mixed $data Unused
6956       * @param string $query
6957       * @return string highlight
6958       */
6959      public function output_html($data, $query='') {
6960          global $CFG, $OUTPUT, $DB;
6961  
6962          // display strings
6963          $txt = get_strings(array('authenticationplugins', 'users', 'administration',
6964              'settings', 'edit', 'name', 'enable', 'disable',
6965              'up', 'down', 'none', 'users'));
6966          $txt->updown = "$txt->up/$txt->down";
6967          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
6968          $txt->testsettings = get_string('testsettings', 'core_auth');
6969  
6970          $authsavailable = core_component::get_plugin_list('auth');
6971          get_enabled_auth_plugins(true); // fix the list of enabled auths
6972          if (empty($CFG->auth)) {
6973              $authsenabled = array();
6974          } else {
6975              $authsenabled = explode(',', $CFG->auth);
6976          }
6977  
6978          // construct the display array, with enabled auth plugins at the top, in order
6979          $displayauths = array();
6980          $registrationauths = array();
6981          $registrationauths[''] = $txt->disable;
6982          $authplugins = array();
6983          foreach ($authsenabled as $auth) {
6984              $authplugin = get_auth_plugin($auth);
6985              $authplugins[$auth] = $authplugin;
6986              /// Get the auth title (from core or own auth lang files)
6987              $authtitle = $authplugin->get_title();
6988              /// Apply titles
6989              $displayauths[$auth] = $authtitle;
6990              if ($authplugin->can_signup()) {
6991                  $registrationauths[$auth] = $authtitle;
6992              }
6993          }
6994  
6995          foreach ($authsavailable as $auth => $dir) {
6996              if (array_key_exists($auth, $displayauths)) {
6997                  continue; //already in the list
6998              }
6999              $authplugin = get_auth_plugin($auth);
7000              $authplugins[$auth] = $authplugin;
7001              /// Get the auth title (from core or own auth lang files)
7002              $authtitle = $authplugin->get_title();
7003              /// Apply titles
7004              $displayauths[$auth] = $authtitle;
7005              if ($authplugin->can_signup()) {
7006                  $registrationauths[$auth] = $authtitle;
7007              }
7008          }
7009  
7010          $return = $OUTPUT->heading(get_string('actauthhdr', 'auth'), 3, 'main');
7011          $return .= $OUTPUT->box_start('generalbox authsui');
7012  
7013          $table = new html_table();
7014          $table->head  = array($txt->name, $txt->users, $txt->enable, $txt->updown, $txt->settings, $txt->testsettings, $txt->uninstall);
7015          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7016          $table->data  = array();
7017          $table->attributes['class'] = 'admintable generaltable';
7018          $table->id = 'manageauthtable';
7019  
7020          //add always enabled plugins first
7021          $displayname = $displayauths['manual'];
7022          $settings = "<a href=\"settings.php?section=authsettingmanual\">{$txt->settings}</a>";
7023          $usercount = $DB->count_records('user', array('auth'=>'manual', 'deleted'=>0));
7024          $table->data[] = array($displayname, $usercount, '', '', $settings, '', '');
7025          $displayname = $displayauths['nologin'];
7026          $usercount = $DB->count_records('user', array('auth'=>'nologin', 'deleted'=>0));
7027          $table->data[] = array($displayname, $usercount, '', '', '', '', '');
7028  
7029  
7030          // iterate through auth plugins and add to the display table
7031          $updowncount = 1;
7032          $authcount = count($authsenabled);
7033          $url = "auth.php?sesskey=" . sesskey();
7034          foreach ($displayauths as $auth => $name) {
7035              if ($auth == 'manual' or $auth == 'nologin') {
7036                  continue;
7037              }
7038              $class = '';
7039              // hide/show link
7040              if (in_array($auth, $authsenabled)) {
7041                  $hideshow = "<a href=\"$url&amp;action=disable&amp;auth=$auth\">";
7042                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7043                  $enabled = true;
7044                  $displayname = $name;
7045              }
7046              else {
7047                  $hideshow = "<a href=\"$url&amp;action=enable&amp;auth=$auth\">";
7048                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7049                  $enabled = false;
7050                  $displayname = $name;
7051                  $class = 'dimmed_text';
7052              }
7053  
7054              $usercount = $DB->count_records('user', array('auth'=>$auth, 'deleted'=>0));
7055  
7056              // up/down link (only if auth is enabled)
7057              $updown = '';
7058              if ($enabled) {
7059                  if ($updowncount > 1) {
7060                      $updown .= "<a href=\"$url&amp;action=up&amp;auth=$auth\">";
7061                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7062                  }
7063                  else {
7064                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7065                  }
7066                  if ($updowncount < $authcount) {
7067                      $updown .= "<a href=\"$url&amp;action=down&amp;auth=$auth\">";
7068                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7069                  }
7070                  else {
7071                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7072                  }
7073                  ++ $updowncount;
7074              }
7075  
7076              // settings link
7077              if (file_exists($CFG->dirroot.'/auth/'.$auth.'/settings.php')) {
7078                  $settings = "<a href=\"settings.php?section=authsetting$auth\">{$txt->settings}</a>";
7079              } else if (file_exists($CFG->dirroot.'/auth/'.$auth.'/config.html')) {
7080                  $settings = "<a href=\"auth_config.php?auth=$auth\">{$txt->settings}</a>";
7081              } else {
7082                  $settings = '';
7083              }
7084  
7085              // Uninstall link.
7086              $uninstall = '';
7087              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('auth_'.$auth, 'manage')) {
7088                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7089              }
7090  
7091              $test = '';
7092              if (!empty($authplugins[$auth]) and method_exists($authplugins[$auth], 'test_settings')) {
7093                  $testurl = new moodle_url('/auth/test_settings.php', array('auth'=>$auth, 'sesskey'=>sesskey()));
7094                  $test = html_writer::link($testurl, $txt->testsettings);
7095              }
7096  
7097              // Add a row to the table.
7098              $row = new html_table_row(array($displayname, $usercount, $hideshow, $updown, $settings, $test, $uninstall));
7099              if ($class) {
7100                  $row->attributes['class'] = $class;
7101              }
7102              $table->data[] = $row;
7103          }
7104          $return .= html_writer::table($table);
7105          $return .= get_string('configauthenticationplugins', 'admin').'<br />'.get_string('tablenosave', 'filters');
7106          $return .= $OUTPUT->box_end();
7107          return highlight($query, $return);
7108      }
7109  }
7110  
7111  
7112  /**
7113   * Special class for authentication administration.
7114   *
7115   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7116   */
7117  class admin_setting_manageeditors extends admin_setting {
7118      /**
7119       * Calls parent::__construct with specific arguments
7120       */
7121      public function __construct() {
7122          $this->nosave = true;
7123          parent::__construct('editorsui', get_string('editorsettings', 'editor'), '', '');
7124      }
7125  
7126      /**
7127       * Always returns true, does nothing
7128       *
7129       * @return true
7130       */
7131      public function get_setting() {
7132          return true;
7133      }
7134  
7135      /**
7136       * Always returns true, does nothing
7137       *
7138       * @return true
7139       */
7140      public function get_defaultsetting() {
7141          return true;
7142      }
7143  
7144      /**
7145       * Always returns '', does not write anything
7146       *
7147       * @return string Always returns ''
7148       */
7149      public function write_setting($data) {
7150      // do not write any setting
7151          return '';
7152      }
7153  
7154      /**
7155       * Checks if $query is one of the available editors
7156       *
7157       * @param string $query The string to search for
7158       * @return bool Returns true if found, false if not
7159       */
7160      public function is_related($query) {
7161          if (parent::is_related($query)) {
7162              return true;
7163          }
7164  
7165          $editors_available = editors_get_available();
7166          foreach ($editors_available as $editor=>$editorstr) {
7167              if (strpos($editor, $query) !== false) {
7168                  return true;
7169              }
7170              if (strpos(core_text::strtolower($editorstr), $query) !== false) {
7171                  return true;
7172              }
7173          }
7174          return false;
7175      }
7176  
7177      /**
7178       * Builds the XHTML to display the control
7179       *
7180       * @param string $data Unused
7181       * @param string $query
7182       * @return string
7183       */
7184      public function output_html($data, $query='') {
7185          global $CFG, $OUTPUT;
7186  
7187          // display strings
7188          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7189              'up', 'down', 'none'));
7190          $struninstall = get_string('uninstallplugin', 'core_admin');
7191  
7192          $txt->updown = "$txt->up/$txt->down";
7193  
7194          $editors_available = editors_get_available();
7195          $active_editors = explode(',', $CFG->texteditors);
7196  
7197          $active_editors = array_reverse($active_editors);
7198          foreach ($active_editors as $key=>$editor) {
7199              if (empty($editors_available[$editor])) {
7200                  unset($active_editors[$key]);
7201              } else {
7202                  $name = $editors_available[$editor];
7203                  unset($editors_available[$editor]);
7204                  $editors_available[$editor] = $name;
7205              }
7206          }
7207          if (empty($active_editors)) {
7208          //$active_editors = array('textarea');
7209          }
7210          $editors_available = array_reverse($editors_available, true);
7211          $return = $OUTPUT->heading(get_string('acteditorshhdr', 'editor'), 3, 'main', true);
7212          $return .= $OUTPUT->box_start('generalbox editorsui');
7213  
7214          $table = new html_table();
7215          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7216          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7217          $table->id = 'editormanagement';
7218          $table->attributes['class'] = 'admintable generaltable';
7219          $table->data  = array();
7220  
7221          // iterate through auth plugins and add to the display table
7222          $updowncount = 1;
7223          $editorcount = count($active_editors);
7224          $url = "editors.php?sesskey=" . sesskey();
7225          foreach ($editors_available as $editor => $name) {
7226          // hide/show link
7227              $class = '';
7228              if (in_array($editor, $active_editors)) {
7229                  $hideshow = "<a href=\"$url&amp;action=disable&amp;editor=$editor\">";
7230                  $hideshow .= $OUTPUT->pix_icon('t/hide', get_string('disable')) . '</a>';
7231                  $enabled = true;
7232                  $displayname = $name;
7233              }
7234              else {
7235                  $hideshow = "<a href=\"$url&amp;action=enable&amp;editor=$editor\">";
7236                  $hideshow .= $OUTPUT->pix_icon('t/show', get_string('enable')) . '</a>';
7237                  $enabled = false;
7238                  $displayname = $name;
7239                  $class = 'dimmed_text';
7240              }
7241  
7242              // up/down link (only if auth is enabled)
7243              $updown = '';
7244              if ($enabled) {
7245                  if ($updowncount > 1) {
7246                      $updown .= "<a href=\"$url&amp;action=up&amp;editor=$editor\">";
7247                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
7248                  }
7249                  else {
7250                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7251                  }
7252                  if ($updowncount < $editorcount) {
7253                      $updown .= "<a href=\"$url&amp;action=down&amp;editor=$editor\">";
7254                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
7255                  }
7256                  else {
7257                      $updown .= $OUTPUT->spacer() . '&nbsp;';
7258                  }
7259                  ++ $updowncount;
7260              }
7261  
7262              // settings link
7263              if (file_exists($CFG->dirroot.'/lib/editor/'.$editor.'/settings.php')) {
7264                  $eurl = new moodle_url('/admin/settings.php', array('section'=>'editorsettings'.$editor));
7265                  $settings = "<a href='$eurl'>{$txt->settings}</a>";
7266              } else {
7267                  $settings = '';
7268              }
7269  
7270              $uninstall = '';
7271              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('editor_'.$editor, 'manage')) {
7272                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7273              }
7274  
7275              // Add a row to the table.
7276              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7277              if ($class) {
7278                  $row->attributes['class'] = $class;
7279              }
7280              $table->data[] = $row;
7281          }
7282          $return .= html_writer::table($table);
7283          $return .= get_string('configeditorplugins', 'editor').'<br />'.get_string('tablenosave', 'admin');
7284          $return .= $OUTPUT->box_end();
7285          return highlight($query, $return);
7286      }
7287  }
7288  
7289  /**
7290   * Special class for antiviruses administration.
7291   *
7292   * @copyright  2015 Ruslan Kabalin, Lancaster University.
7293   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7294   */
7295  class admin_setting_manageantiviruses extends admin_setting {
7296      /**
7297       * Calls parent::__construct with specific arguments
7298       */
7299      public function __construct() {
7300          $this->nosave = true;
7301          parent::__construct('antivirusesui', get_string('antivirussettings', 'antivirus'), '', '');
7302      }
7303  
7304      /**
7305       * Always returns true, does nothing
7306       *
7307       * @return true
7308       */
7309      public function get_setting() {
7310          return true;
7311      }
7312  
7313      /**
7314       * Always returns true, does nothing
7315       *
7316       * @return true
7317       */
7318      public function get_defaultsetting() {
7319          return true;
7320      }
7321  
7322      /**
7323       * Always returns '', does not write anything
7324       *
7325       * @param string $data Unused
7326       * @return string Always returns ''
7327       */
7328      public function write_setting($data) {
7329          // Do not write any setting.
7330          return '';
7331      }
7332  
7333      /**
7334       * Checks if $query is one of the available editors
7335       *
7336       * @param string $query The string to search for
7337       * @return bool Returns true if found, false if not
7338       */
7339      public function is_related($query) {
7340          if (parent::is_related($query)) {
7341              return true;
7342          }
7343  
7344          $antivirusesavailable = \core\antivirus\manager::get_available();
7345          foreach ($antivirusesavailable as $antivirus => $antivirusstr) {
7346              if (strpos($antivirus, $query) !== false) {
7347                  return true;
7348              }
7349              if (strpos(core_text::strtolower($antivirusstr), $query) !== false) {
7350                  return true;
7351              }
7352          }
7353          return false;
7354      }
7355  
7356      /**
7357       * Builds the XHTML to display the control
7358       *
7359       * @param string $data Unused
7360       * @param string $query
7361       * @return string
7362       */
7363      public function output_html($data, $query='') {
7364          global $CFG, $OUTPUT;
7365  
7366          // Display strings.
7367          $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable',
7368              'up', 'down', 'none'));
7369          $struninstall = get_string('uninstallplugin', 'core_admin');
7370  
7371          $txt->updown = "$txt->up/$txt->down";
7372  
7373          $antivirusesavailable = \core\antivirus\manager::get_available();
7374          $activeantiviruses = explode(',', $CFG->antiviruses);
7375  
7376          $activeantiviruses = array_reverse($activeantiviruses);
7377          foreach ($activeantiviruses as $key => $antivirus) {
7378              if (empty($antivirusesavailable[$antivirus])) {
7379                  unset($activeantiviruses[$key]);
7380              } else {
7381                  $name = $antivirusesavailable[$antivirus];
7382                  unset($antivirusesavailable[$antivirus]);
7383                  $antivirusesavailable[$antivirus] = $name;
7384              }
7385          }
7386          $antivirusesavailable = array_reverse($antivirusesavailable, true);
7387          $return = $OUTPUT->heading(get_string('actantivirushdr', 'antivirus'), 3, 'main', true);
7388          $return .= $OUTPUT->box_start('generalbox antivirusesui');
7389  
7390          $table = new html_table();
7391          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall);
7392          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
7393          $table->id = 'antivirusmanagement';
7394          $table->attributes['class'] = 'admintable generaltable';
7395          $table->data  = array();
7396  
7397          // Iterate through auth plugins and add to the display table.
7398          $updowncount = 1;
7399          $antiviruscount = count($activeantiviruses);
7400          $baseurl = new moodle_url('/admin/antiviruses.php', array('sesskey' => sesskey()));
7401          foreach ($antivirusesavailable as $antivirus => $name) {
7402              // Hide/show link.
7403              $class = '';
7404              if (in_array($antivirus, $activeantiviruses)) {
7405                  $hideshowurl = $baseurl;
7406                  $hideshowurl->params(array('action' => 'disable', 'antivirus' => $antivirus));
7407                  $hideshowimg = $OUTPUT->pix_icon('t/hide', get_string('disable'));
7408                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7409                  $enabled = true;
7410                  $displayname = $name;
7411              } else {
7412                  $hideshowurl = $baseurl;
7413                  $hideshowurl->params(array('action' => 'enable', 'antivirus' => $antivirus));
7414                  $hideshowimg = $OUTPUT->pix_icon('t/show', get_string('enable'));
7415                  $hideshow = html_writer::link($hideshowurl, $hideshowimg);
7416                  $enabled = false;
7417                  $displayname = $name;
7418                  $class = 'dimmed_text';
7419              }
7420  
7421              // Up/down link.
7422              $updown = '';
7423              if ($enabled) {
7424                  if ($updowncount > 1) {
7425                      $updownurl = $baseurl;
7426                      $updownurl->params(array('action' => 'up', 'antivirus' => $antivirus));
7427                      $updownimg = $OUTPUT->pix_icon('t/up', get_string('moveup'));
7428                      $updown = html_writer::link($updownurl, $updownimg);
7429                  } else {
7430                      $updownimg = $OUTPUT->spacer();
7431                  }
7432                  if ($updowncount < $antiviruscount) {
7433                      $updownurl = $baseurl;
7434                      $updownurl->params(array('action' => 'down', 'antivirus' => $antivirus));
7435                      $updownimg = $OUTPUT->pix_icon('t/down', get_string('movedown'));
7436                      $updown = html_writer::link($updownurl, $updownimg);
7437                  } else {
7438                      $updownimg = $OUTPUT->spacer();
7439                  }
7440                  ++ $updowncount;
7441              }
7442  
7443              // Settings link.
7444              if (file_exists($CFG->dirroot.'/lib/antivirus/'.$antivirus.'/settings.php')) {
7445                  $eurl = new moodle_url('/admin/settings.php', array('section' => 'antivirussettings'.$antivirus));
7446                  $settings = html_writer::link($eurl, $txt->settings);
7447              } else {
7448                  $settings = '';
7449              }
7450  
7451              $uninstall = '';
7452              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('antivirus_'.$antivirus, 'manage')) {
7453                  $uninstall = html_writer::link($uninstallurl, $struninstall);
7454              }
7455  
7456              // Add a row to the table.
7457              $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall));
7458              if ($class) {
7459                  $row->attributes['class'] = $class;
7460              }
7461              $table->data[] = $row;
7462          }
7463          $return .= html_writer::table($table);
7464          $return .= get_string('configantivirusplugins', 'antivirus') . html_writer::empty_tag('br') . get_string('tablenosave', 'admin');
7465          $return .= $OUTPUT->box_end();
7466          return highlight($query, $return);
7467      }
7468  }
7469  
7470  /**
7471   * Special class for license administration.
7472   *
7473   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7474   * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7475   * @todo MDL-45184 This class will be deleted in Moodle 4.1.
7476   */
7477  class admin_setting_managelicenses extends admin_setting {
7478      /**
7479       * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7480       * @todo MDL-45184 This class will be deleted in Moodle 4.1
7481       */
7482      public function __construct() {
7483          global $ADMIN;
7484  
7485          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7486              DEBUG_DEVELOPER);
7487  
7488          // Replace admin setting load with new external page load for tool_licensemanager, if not loaded already.
7489          if (!is_null($ADMIN->locate('licensemanager'))) {
7490              $temp = new admin_externalpage('licensemanager',
7491                  get_string('licensemanager', 'tool_licensemanager'),
7492                  \tool_licensemanager\helper::get_licensemanager_url());
7493  
7494              $ADMIN->add('license', $temp);
7495          }
7496      }
7497  
7498      /**
7499       * Always returns true, does nothing
7500       *
7501       * @deprecated since Moodle 3.9 MDL-45184.
7502       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7503       *
7504       * @return true
7505       */
7506      public function get_setting() {
7507          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7508              DEBUG_DEVELOPER);
7509  
7510          return true;
7511      }
7512  
7513      /**
7514       * Always returns true, does nothing
7515       *
7516       * @deprecated since Moodle 3.9 MDL-45184.
7517       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7518       *
7519       * @return true
7520       */
7521      public function get_defaultsetting() {
7522          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7523              DEBUG_DEVELOPER);
7524  
7525          return true;
7526      }
7527  
7528      /**
7529       * Always returns '', does not write anything
7530       *
7531       * @deprecated since Moodle 3.9 MDL-45184.
7532       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7533       *
7534       * @return string Always returns ''
7535       */
7536      public function write_setting($data) {
7537          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7538              DEBUG_DEVELOPER);
7539  
7540          // do not write any setting
7541          return '';
7542      }
7543  
7544      /**
7545       * Builds the XHTML to display the control
7546       *
7547       * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
7548       * @todo MDL-45184 This method will be deleted in Moodle 4.1
7549       *
7550       * @param string $data Unused
7551       * @param string $query
7552       * @return string
7553       */
7554      public function output_html($data, $query='') {
7555          debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
7556              DEBUG_DEVELOPER);
7557  
7558          redirect(\tool_licensemanager\helper::get_licensemanager_url());
7559      }
7560  }
7561  
7562  /**
7563   * Course formats manager. Allows to enable/disable formats and jump to settings
7564   */
7565  class admin_setting_manageformats extends admin_setting {
7566  
7567      /**
7568       * Calls parent::__construct with specific arguments
7569       */
7570      public function __construct() {
7571          $this->nosave = true;
7572          parent::__construct('formatsui', new lang_string('manageformats', 'core_admin'), '', '');
7573      }
7574  
7575      /**
7576       * Always returns true
7577       *
7578       * @return true
7579       */
7580      public function get_setting() {
7581          return true;
7582      }
7583  
7584      /**
7585       * Always returns true
7586       *
7587       * @return true
7588       */
7589      public function get_defaultsetting() {
7590          return true;
7591      }
7592  
7593      /**
7594       * Always returns '' and doesn't write anything
7595       *
7596       * @param mixed $data string or array, must not be NULL
7597       * @return string Always returns ''
7598       */
7599      public function write_setting($data) {
7600          // do not write any setting
7601          return '';
7602      }
7603  
7604      /**
7605       * Search to find if Query is related to format plugin
7606       *
7607       * @param string $query The string to search for
7608       * @return bool true for related false for not
7609       */
7610      public function is_related($query) {
7611          if (parent::is_related($query)) {
7612              return true;
7613          }
7614          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7615          foreach ($formats as $format) {
7616              if (strpos($format->component, $query) !== false ||
7617                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7618                  return true;
7619              }
7620          }
7621          return false;
7622      }
7623  
7624      /**
7625       * Return XHTML to display control
7626       *
7627       * @param mixed $data Unused
7628       * @param string $query
7629       * @return string highlight
7630       */
7631      public function output_html($data, $query='') {
7632          global $CFG, $OUTPUT;
7633          $return = '';
7634          $return = $OUTPUT->heading(new lang_string('courseformats'), 3, 'main');
7635          $return .= $OUTPUT->box_start('generalbox formatsui');
7636  
7637          $formats = core_plugin_manager::instance()->get_plugins_of_type('format');
7638  
7639          // display strings
7640          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
7641          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7642          $txt->updown = "$txt->up/$txt->down";
7643  
7644          $table = new html_table();
7645          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
7646          $table->align = array('left', 'center', 'center', 'center', 'center');
7647          $table->attributes['class'] = 'manageformattable generaltable admintable';
7648          $table->data  = array();
7649  
7650          $cnt = 0;
7651          $defaultformat = get_config('moodlecourse', 'format');
7652          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7653          foreach ($formats as $format) {
7654              $url = new moodle_url('/admin/courseformats.php',
7655                      array('sesskey' => sesskey(), 'format' => $format->name));
7656              $isdefault = '';
7657              $class = '';
7658              if ($format->is_enabled()) {
7659                  $strformatname = $format->displayname;
7660                  if ($defaultformat === $format->name) {
7661                      $hideshow = $txt->default;
7662                  } else {
7663                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7664                              $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7665                  }
7666              } else {
7667                  $strformatname = $format->displayname;
7668                  $class = 'dimmed_text';
7669                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7670                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7671              }
7672              $updown = '';
7673              if ($cnt) {
7674                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
7675                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
7676              } else {
7677                  $updown .= $spacer;
7678              }
7679              if ($cnt < count($formats) - 1) {
7680                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
7681                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
7682              } else {
7683                  $updown .= $spacer;
7684              }
7685              $cnt++;
7686              $settings = '';
7687              if ($format->get_settings_url()) {
7688                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
7689              }
7690              $uninstall = '';
7691              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('format_'.$format->name, 'manage')) {
7692                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7693              }
7694              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
7695              if ($class) {
7696                  $row->attributes['class'] = $class;
7697              }
7698              $table->data[] = $row;
7699          }
7700          $return .= html_writer::table($table);
7701          $link = html_writer::link(new moodle_url('/admin/settings.php', array('section' => 'coursesettings')), new lang_string('coursesettings'));
7702          $return .= html_writer::tag('p', get_string('manageformatsgotosettings', 'admin', $link));
7703          $return .= $OUTPUT->box_end();
7704          return highlight($query, $return);
7705      }
7706  }
7707  
7708  /**
7709   * Custom fields manager. Allows to enable/disable custom fields and jump to settings.
7710   *
7711   * @package    core
7712   * @copyright  2018 Toni Barbera
7713   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7714   */
7715  class admin_setting_managecustomfields extends admin_setting {
7716  
7717      /**
7718       * Calls parent::__construct with specific arguments
7719       */
7720      public function __construct() {
7721          $this->nosave = true;
7722          parent::__construct('customfieldsui', new lang_string('managecustomfields', 'core_admin'), '', '');
7723      }
7724  
7725      /**
7726       * Always returns true
7727       *
7728       * @return true
7729       */
7730      public function get_setting() {
7731          return true;
7732      }
7733  
7734      /**
7735       * Always returns true
7736       *
7737       * @return true
7738       */
7739      public function get_defaultsetting() {
7740          return true;
7741      }
7742  
7743      /**
7744       * Always returns '' and doesn't write anything
7745       *
7746       * @param mixed $data string or array, must not be NULL
7747       * @return string Always returns ''
7748       */
7749      public function write_setting($data) {
7750          // Do not write any setting.
7751          return '';
7752      }
7753  
7754      /**
7755       * Search to find if Query is related to format plugin
7756       *
7757       * @param string $query The string to search for
7758       * @return bool true for related false for not
7759       */
7760      public function is_related($query) {
7761          if (parent::is_related($query)) {
7762              return true;
7763          }
7764          $formats = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7765          foreach ($formats as $format) {
7766              if (strpos($format->component, $query) !== false ||
7767                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7768                  return true;
7769              }
7770          }
7771          return false;
7772      }
7773  
7774      /**
7775       * Return XHTML to display control
7776       *
7777       * @param mixed $data Unused
7778       * @param string $query
7779       * @return string highlight
7780       */
7781      public function output_html($data, $query='') {
7782          global $CFG, $OUTPUT;
7783          $return = '';
7784          $return = $OUTPUT->heading(new lang_string('customfields', 'core_customfield'), 3, 'main');
7785          $return .= $OUTPUT->box_start('generalbox customfieldsui');
7786  
7787          $fields = core_plugin_manager::instance()->get_plugins_of_type('customfield');
7788  
7789          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down'));
7790          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7791          $txt->updown = "$txt->up/$txt->down";
7792  
7793          $table = new html_table();
7794          $table->head  = array($txt->name, $txt->enable, $txt->uninstall, $txt->settings);
7795          $table->align = array('left', 'center', 'center', 'center');
7796          $table->attributes['class'] = 'managecustomfieldtable generaltable admintable';
7797          $table->data  = array();
7798  
7799          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7800          foreach ($fields as $field) {
7801              $url = new moodle_url('/admin/customfields.php',
7802                      array('sesskey' => sesskey(), 'field' => $field->name));
7803  
7804              if ($field->is_enabled()) {
7805                  $strfieldname = $field->displayname;
7806                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7807                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7808              } else {
7809                  $strfieldname = $field->displayname;
7810                  $class = 'dimmed_text';
7811                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7812                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7813              }
7814              $settings = '';
7815              if ($field->get_settings_url()) {
7816                  $settings = html_writer::link($field->get_settings_url(), $txt->settings);
7817              }
7818              $uninstall = '';
7819              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('customfield_'.$field->name, 'manage')) {
7820                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7821              }
7822              $row = new html_table_row(array($strfieldname, $hideshow, $uninstall, $settings));
7823              $table->data[] = $row;
7824          }
7825          $return .= html_writer::table($table);
7826          $return .= $OUTPUT->box_end();
7827          return highlight($query, $return);
7828      }
7829  }
7830  
7831  /**
7832   * Data formats manager. Allow reorder and to enable/disable data formats and jump to settings
7833   *
7834   * @copyright  2016 Brendan Heywood (brendan@catalyst-au.net)
7835   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7836   */
7837  class admin_setting_managedataformats extends admin_setting {
7838  
7839      /**
7840       * Calls parent::__construct with specific arguments
7841       */
7842      public function __construct() {
7843          $this->nosave = true;
7844          parent::__construct('managedataformats', new lang_string('managedataformats'), '', '');
7845      }
7846  
7847      /**
7848       * Always returns true
7849       *
7850       * @return true
7851       */
7852      public function get_setting() {
7853          return true;
7854      }
7855  
7856      /**
7857       * Always returns true
7858       *
7859       * @return true
7860       */
7861      public function get_defaultsetting() {
7862          return true;
7863      }
7864  
7865      /**
7866       * Always returns '' and doesn't write anything
7867       *
7868       * @param mixed $data string or array, must not be NULL
7869       * @return string Always returns ''
7870       */
7871      public function write_setting($data) {
7872          // Do not write any setting.
7873          return '';
7874      }
7875  
7876      /**
7877       * Search to find if Query is related to format plugin
7878       *
7879       * @param string $query The string to search for
7880       * @return bool true for related false for not
7881       */
7882      public function is_related($query) {
7883          if (parent::is_related($query)) {
7884              return true;
7885          }
7886          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
7887          foreach ($formats as $format) {
7888              if (strpos($format->component, $query) !== false ||
7889                      strpos(core_text::strtolower($format->displayname), $query) !== false) {
7890                  return true;
7891              }
7892          }
7893          return false;
7894      }
7895  
7896      /**
7897       * Return XHTML to display control
7898       *
7899       * @param mixed $data Unused
7900       * @param string $query
7901       * @return string highlight
7902       */
7903      public function output_html($data, $query='') {
7904          global $CFG, $OUTPUT;
7905          $return = '';
7906  
7907          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
7908  
7909          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default'));
7910          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
7911          $txt->updown = "$txt->up/$txt->down";
7912  
7913          $table = new html_table();
7914          $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->uninstall, $txt->settings);
7915          $table->align = array('left', 'center', 'center', 'center', 'center');
7916          $table->attributes['class'] = 'manageformattable generaltable admintable';
7917          $table->data  = array();
7918  
7919          $cnt = 0;
7920          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
7921          $totalenabled = 0;
7922          foreach ($formats as $format) {
7923              if ($format->is_enabled() && $format->is_installed_and_upgraded()) {
7924                  $totalenabled++;
7925              }
7926          }
7927          foreach ($formats as $format) {
7928              $status = $format->get_status();
7929              $url = new moodle_url('/admin/dataformats.php',
7930                      array('sesskey' => sesskey(), 'name' => $format->name));
7931  
7932              $class = '';
7933              if ($format->is_enabled()) {
7934                  $strformatname = $format->displayname;
7935                  if ($totalenabled == 1&& $format->is_enabled()) {
7936                      $hideshow = '';
7937                  } else {
7938                      $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
7939                          $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
7940                  }
7941              } else {
7942                  $class = 'dimmed_text';
7943                  $strformatname = $format->displayname;
7944                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
7945                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
7946              }
7947  
7948              $updown = '';
7949              if ($cnt) {
7950                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
7951                      $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
7952              } else {
7953                  $updown .= $spacer;
7954              }
7955              if ($cnt < count($formats) - 1) {
7956                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
7957                      $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
7958              } else {
7959                  $updown .= $spacer;
7960              }
7961  
7962              $uninstall = '';
7963              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
7964                  $uninstall = get_string('status_missing', 'core_plugin');
7965              } else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
7966                  $uninstall = get_string('status_new', 'core_plugin');
7967              } else if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('dataformat_'.$format->name, 'manage')) {
7968                  if ($totalenabled != 1 || !$format->is_enabled()) {
7969                      $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
7970                  }
7971              }
7972  
7973              $settings = '';
7974              if ($format->get_settings_url()) {
7975                  $settings = html_writer::link($format->get_settings_url(), $txt->settings);
7976              }
7977  
7978              $row = new html_table_row(array($strformatname, $hideshow, $updown, $uninstall, $settings));
7979              if ($class) {
7980                  $row->attributes['class'] = $class;
7981              }
7982              $table->data[] = $row;
7983              $cnt++;
7984          }
7985          $return .= html_writer::table($table);
7986          return highlight($query, $return);
7987      }
7988  }
7989  
7990  /**
7991   * Special class for filter administration.
7992   *
7993   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7994   */
7995  class admin_page_managefilters extends admin_externalpage {
7996      /**
7997       * Calls parent::__construct with specific arguments
7998       */
7999      public function __construct() {
8000          global $CFG;
8001          parent::__construct('managefilters', get_string('filtersettings', 'admin'), "$CFG->wwwroot/$CFG->admin/filters.php");
8002      }
8003  
8004      /**
8005       * Searches all installed filters for specified filter
8006       *
8007       * @param string $query The filter(string) to search for
8008       * @param string $query
8009       */
8010      public function search($query) {
8011          global $CFG;
8012          if ($result = parent::search($query)) {
8013              return $result;
8014          }
8015  
8016          $found = false;
8017          $filternames = filter_get_all_installed();
8018          foreach ($filternames as $path => $strfiltername) {
8019              if (strpos(core_text::strtolower($strfiltername), $query) !== false) {
8020                  $found = true;
8021                  break;
8022              }
8023              if (strpos($path, $query) !== false) {
8024                  $found = true;
8025                  break;
8026              }
8027          }
8028  
8029          if ($found) {
8030              $result = new stdClass;
8031              $result->page = $this;
8032              $result->settings = array();
8033              return array($this->name => $result);
8034          } else {
8035              return array();
8036          }
8037      }
8038  }
8039  
8040  /**
8041   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8042   * Requires a get_rank method on the plugininfo class for sorting.
8043   *
8044   * @copyright 2017 Damyon Wiese
8045   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8046   */
8047  abstract class admin_setting_manage_plugins extends admin_setting {
8048  
8049      /**
8050       * Get the admin settings section name (just a unique string)
8051       *
8052       * @return string
8053       */
8054      public function get_section_name() {
8055          return 'manage' . $this->get_plugin_type() . 'plugins';
8056      }
8057  
8058      /**
8059       * Get the admin settings section title (use get_string).
8060       *
8061       * @return string
8062       */
8063      abstract public function get_section_title();
8064  
8065      /**
8066       * Get the type of plugin to manage.
8067       *
8068       * @return string
8069       */
8070      abstract public function get_plugin_type();
8071  
8072      /**
8073       * Get the name of the second column.
8074       *
8075       * @return string
8076       */
8077      public function get_info_column_name() {
8078          return '';
8079      }
8080  
8081      /**
8082       * Get the type of plugin to manage.
8083       *
8084       * @param plugininfo The plugin info class.
8085       * @return string
8086       */
8087      abstract public function get_info_column($plugininfo);
8088  
8089      /**
8090       * Calls parent::__construct with specific arguments
8091       */
8092      public function __construct() {
8093          $this->nosave = true;
8094          parent::__construct($this->get_section_name(), $this->get_section_title(), '', '');
8095      }
8096  
8097      /**
8098       * Always returns true, does nothing
8099       *
8100       * @return true
8101       */
8102      public function get_setting() {
8103          return true;
8104      }
8105  
8106      /**
8107       * Always returns true, does nothing
8108       *
8109       * @return true
8110       */
8111      public function get_defaultsetting() {
8112          return true;
8113      }
8114  
8115      /**
8116       * Always returns '', does not write anything
8117       *
8118       * @param mixed $data
8119       * @return string Always returns ''
8120       */
8121      public function write_setting($data) {
8122          // Do not write any setting.
8123          return '';
8124      }
8125  
8126      /**
8127       * Checks if $query is one of the available plugins of this type
8128       *
8129       * @param string $query The string to search for
8130       * @return bool Returns true if found, false if not
8131       */
8132      public function is_related($query) {
8133          if (parent::is_related($query)) {
8134              return true;
8135          }
8136  
8137          $query = core_text::strtolower($query);
8138          $plugins = core_plugin_manager::instance()->get_plugins_of_type($this->get_plugin_type());
8139          foreach ($plugins as $name => $plugin) {
8140              $localised = $plugin->displayname;
8141              if (strpos(core_text::strtolower($name), $query) !== false) {
8142                  return true;
8143              }
8144              if (strpos(core_text::strtolower($localised), $query) !== false) {
8145                  return true;
8146              }
8147          }
8148          return false;
8149      }
8150  
8151      /**
8152       * The URL for the management page for this plugintype.
8153       *
8154       * @return moodle_url
8155       */
8156      protected function get_manage_url() {
8157          return new moodle_url('/admin/updatesetting.php');
8158      }
8159  
8160      /**
8161       * Builds the HTML to display the control.
8162       *
8163       * @param string $data Unused
8164       * @param string $query
8165       * @return string
8166       */
8167      public function output_html($data, $query = '') {
8168          global $CFG, $OUTPUT, $DB, $PAGE;
8169  
8170          $context = (object) [
8171              'manageurl' => new moodle_url($this->get_manage_url(), [
8172                      'type' => $this->get_plugin_type(),
8173                      'sesskey' => sesskey(),
8174                  ]),
8175              'infocolumnname' => $this->get_info_column_name(),
8176              'plugins' => [],
8177          ];
8178  
8179          $pluginmanager = core_plugin_manager::instance();
8180          $allplugins = $pluginmanager->get_plugins_of_type($this->get_plugin_type());
8181          $enabled = $pluginmanager->get_enabled_plugins($this->get_plugin_type());
8182          $plugins = array_merge($enabled, $allplugins);
8183          foreach ($plugins as $key => $plugin) {
8184              $pluginlink = new moodle_url($context->manageurl, ['plugin' => $key]);
8185  
8186              $pluginkey = (object) [
8187                  'plugin' => $plugin->displayname,
8188                  'enabled' => $plugin->is_enabled(),
8189                  'togglelink' => '',
8190                  'moveuplink' => '',
8191                  'movedownlink' => '',
8192                  'settingslink' => $plugin->get_settings_url(),
8193                  'uninstalllink' => '',
8194                  'info' => '',
8195              ];
8196  
8197              // Enable/Disable link.
8198              $togglelink = new moodle_url($pluginlink);
8199              if ($plugin->is_enabled()) {
8200                  $toggletarget = false;
8201                  $togglelink->param('action', 'disable');
8202  
8203                  if (count($context->plugins)) {
8204                      // This is not the first plugin.
8205                      $pluginkey->moveuplink = new moodle_url($pluginlink, ['action' => 'up']);
8206                  }
8207  
8208                  if (count($enabled) > count($context->plugins) + 1) {
8209                      // This is not the last plugin.
8210                      $pluginkey->movedownlink = new moodle_url($pluginlink, ['action' => 'down']);
8211                  }
8212  
8213                  $pluginkey->info = $this->get_info_column($plugin);
8214              } else {
8215                  $toggletarget = true;
8216                  $togglelink->param('action', 'enable');
8217              }
8218  
8219              $pluginkey->toggletarget = $toggletarget;
8220              $pluginkey->togglelink = $togglelink;
8221  
8222              $frankenstyle = $plugin->type . '_' . $plugin->name;
8223              if ($uninstalllink = core_plugin_manager::instance()->get_uninstall_url($frankenstyle, 'manage')) {
8224                  // This plugin supports uninstallation.
8225                  $pluginkey->uninstalllink = $uninstalllink;
8226              }
8227  
8228              if (!empty($this->get_info_column_name())) {
8229                  // This plugintype has an info column.
8230                  $pluginkey->info = $this->get_info_column($plugin);
8231              }
8232  
8233              $context->plugins[] = $pluginkey;
8234          }
8235  
8236          $str = $OUTPUT->render_from_template('core_admin/setting_manage_plugins', $context);
8237          return highlight($query, $str);
8238      }
8239  }
8240  
8241  /**
8242   * Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
8243   * Requires a get_rank method on the plugininfo class for sorting.
8244   *
8245   * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
8246  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8247   */
8248  class admin_setting_manage_fileconverter_plugins extends admin_setting_manage_plugins {
8249      public function get_section_title() {
8250          return get_string('type_fileconverter_plural', 'plugin');
8251      }
8252  
8253      public function get_plugin_type() {
8254          return 'fileconverter';
8255      }
8256  
8257      public function get_info_column_name() {
8258          return get_string('supportedconversions', 'plugin');
8259      }
8260  
8261      public function get_info_column($plugininfo) {
8262          return $plugininfo->get_supported_conversions();
8263      }
8264  }
8265  
8266  /**
8267   * Special class for media player plugins management.
8268   *
8269   * @copyright 2016 Marina Glancy
8270   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8271   */
8272  class admin_setting_managemediaplayers extends admin_setting {
8273      /**
8274       * Calls parent::__construct with specific arguments
8275       */
8276      public function __construct() {
8277          $this->nosave = true;
8278          parent::__construct('managemediaplayers', get_string('managemediaplayers', 'media'), '', '');
8279      }
8280  
8281      /**
8282       * Always returns true, does nothing
8283       *
8284       * @return true
8285       */
8286      public function get_setting() {
8287          return true;
8288      }
8289  
8290      /**
8291       * Always returns true, does nothing
8292       *
8293       * @return true
8294       */
8295      public function get_defaultsetting() {
8296          return true;
8297      }
8298  
8299      /**
8300       * Always returns '', does not write anything
8301       *
8302       * @param mixed $data
8303       * @return string Always returns ''
8304       */
8305      public function write_setting($data) {
8306          // Do not write any setting.
8307          return '';
8308      }
8309  
8310      /**
8311       * Checks if $query is one of the available enrol plugins
8312       *
8313       * @param string $query The string to search for
8314       * @return bool Returns true if found, false if not
8315       */
8316      public function is_related($query) {
8317          if (parent::is_related($query)) {
8318              return true;
8319          }
8320  
8321          $query = core_text::strtolower($query);
8322          $plugins = core_plugin_manager::instance()->get_plugins_of_type('media');
8323          foreach ($plugins as $name => $plugin) {
8324              $localised = $plugin->displayname;
8325              if (strpos(core_text::strtolower($name), $query) !== false) {
8326                  return true;
8327              }
8328              if (strpos(core_text::strtolower($localised), $query) !== false) {
8329                  return true;
8330              }
8331          }
8332          return false;
8333      }
8334  
8335      /**
8336       * Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8337       * @return \core\plugininfo\media[]
8338       */
8339      protected function get_sorted_plugins() {
8340          $pluginmanager = core_plugin_manager::instance();
8341  
8342          $plugins = $pluginmanager->get_plugins_of_type('media');
8343          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8344  
8345          // Sort plugins so enabled plugins are displayed first and all others are displayed in the end sorted by rank.
8346          \core_collator::asort_objects_by_method($plugins, 'get_rank', \core_collator::SORT_NUMERIC);
8347  
8348          $order = array_values($enabledplugins);
8349          $order = array_merge($order, array_diff(array_reverse(array_keys($plugins)), $order));
8350  
8351          $sortedplugins = array();
8352          foreach ($order as $name) {
8353              $sortedplugins[$name] = $plugins[$name];
8354          }
8355  
8356          return $sortedplugins;
8357      }
8358  
8359      /**
8360       * Builds the XHTML to display the control
8361       *
8362       * @param string $data Unused
8363       * @param string $query
8364       * @return string
8365       */
8366      public function output_html($data, $query='') {
8367          global $CFG, $OUTPUT, $DB, $PAGE;
8368  
8369          // Display strings.
8370          $strup        = get_string('up');
8371          $strdown      = get_string('down');
8372          $strsettings  = get_string('settings');
8373          $strenable    = get_string('enable');
8374          $strdisable   = get_string('disable');
8375          $struninstall = get_string('uninstallplugin', 'core_admin');
8376          $strversion   = get_string('version');
8377          $strname      = get_string('name');
8378          $strsupports  = get_string('supports', 'core_media');
8379  
8380          $pluginmanager = core_plugin_manager::instance();
8381  
8382          $plugins = $this->get_sorted_plugins();
8383          $enabledplugins = $pluginmanager->get_enabled_plugins('media');
8384  
8385          $return = $OUTPUT->box_start('generalbox mediaplayersui');
8386  
8387          $table = new html_table();
8388          $table->head  = array($strname, $strsupports, $strversion,
8389              $strenable, $strup.'/'.$strdown, $strsettings, $struninstall);
8390          $table->colclasses = array('leftalign', 'leftalign', 'centeralign',
8391              'centeralign', 'centeralign', 'centeralign', 'centeralign');
8392          $table->id = 'mediaplayerplugins';
8393          $table->attributes['class'] = 'admintable generaltable';
8394          $table->data  = array();
8395  
8396          // Iterate through media plugins and add to the display table.
8397          $updowncount = 1;
8398          $url = new moodle_url('/admin/media.php', array('sesskey' => sesskey()));
8399          $printed = array();
8400          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8401  
8402          $usedextensions = [];
8403          foreach ($plugins as $name => $plugin) {
8404              $url->param('media', $name);
8405              $plugininfo = $pluginmanager->get_plugin_info('media_'.$name);
8406              $version = $plugininfo->versiondb;
8407              $supports = $plugininfo->supports($usedextensions);
8408  
8409              // Hide/show links.
8410              $class = '';
8411              if (!$plugininfo->is_installed_and_upgraded()) {
8412                  $hideshow = '';
8413                  $enabled = false;
8414                  $displayname = '<span class="notifyproblem">'.$name.'</span>';
8415              } else {
8416                  $enabled = $plugininfo->is_enabled();
8417                  if ($enabled) {
8418                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'disable')),
8419                          $OUTPUT->pix_icon('t/hide', $strdisable, 'moodle', array('class' => 'iconsmall')));
8420                  } else {
8421                      $hideshow = html_writer::link(new moodle_url($url, array('action' => 'enable')),
8422                          $OUTPUT->pix_icon('t/show', $strenable, 'moodle', array('class' => 'iconsmall')));
8423                      $class = 'dimmed_text';
8424                  }
8425                  $displayname = $plugin->displayname;
8426                  if (get_string_manager()->string_exists('pluginname_help', 'media_' . $name)) {
8427                      $displayname .= '&nbsp;' . $OUTPUT->help_icon('pluginname', 'media_' . $name);
8428                  }
8429              }
8430              if ($PAGE->theme->resolve_image_location('icon', 'media_' . $name, false)) {
8431                  $icon = $OUTPUT->pix_icon('icon', '', 'media_' . $name, array('class' => 'icon pluginicon'));
8432              } else {
8433                  $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon pluginicon noicon'));
8434              }
8435  
8436              // Up/down link (only if enrol is enabled).
8437              $updown = '';
8438              if ($enabled) {
8439                  if ($updowncount > 1) {
8440                      $updown = html_writer::link(new moodle_url($url, array('action' => 'up')),
8441                          $OUTPUT->pix_icon('t/up', $strup, 'moodle', array('class' => 'iconsmall')));
8442                  } else {
8443                      $updown = $spacer;
8444                  }
8445                  if ($updowncount < count($enabledplugins)) {
8446                      $updown .= html_writer::link(new moodle_url($url, array('action' => 'down')),
8447                          $OUTPUT->pix_icon('t/down', $strdown, 'moodle', array('class' => 'iconsmall')));
8448                  } else {
8449                      $updown .= $spacer;
8450                  }
8451                  ++$updowncount;
8452              }
8453  
8454              $uninstall = '';
8455              $status = $plugininfo->get_status();
8456              if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
8457                  $uninstall = get_string('status_missing', 'core_plugin') . '<br/>';
8458              }
8459              if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
8460                  $uninstall = get_string('status_new', 'core_plugin');
8461              } else if ($uninstallurl = $pluginmanager->get_uninstall_url('media_'.$name, 'manage')) {
8462                  $uninstall .= html_writer::link($uninstallurl, $struninstall);
8463              }
8464  
8465              $settings = '';
8466              if ($plugininfo->get_settings_url()) {
8467                  $settings = html_writer::link($plugininfo->get_settings_url(), $strsettings);
8468              }
8469  
8470              // Add a row to the table.
8471              $row = new html_table_row(array($icon.$displayname, $supports, $version, $hideshow, $updown, $settings, $uninstall));
8472              if ($class) {
8473                  $row->attributes['class'] = $class;
8474              }
8475              $table->data[] = $row;
8476  
8477              $printed[$name] = true;
8478          }
8479  
8480          $return .= html_writer::table($table);
8481          $return .= $OUTPUT->box_end();
8482          return highlight($query, $return);
8483      }
8484  }
8485  
8486  
8487  /**
8488   * Content bank content types manager. Allow reorder and to enable/disable content bank content types and jump to settings
8489   *
8490   * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
8491   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8492   */
8493  class admin_setting_managecontentbankcontenttypes extends admin_setting {
8494  
8495      /**
8496       * Calls parent::__construct with specific arguments
8497       */
8498      public function __construct() {
8499          $this->nosave = true;
8500          parent::__construct('contentbank', new lang_string('managecontentbanktypes'), '', '');
8501      }
8502  
8503      /**
8504       * Always returns true
8505       *
8506       * @return true
8507       */
8508      public function get_setting() {
8509          return true;
8510      }
8511  
8512      /**
8513       * Always returns true
8514       *
8515       * @return true
8516       */
8517      public function get_defaultsetting() {
8518          return true;
8519      }
8520  
8521      /**
8522       * Always returns '' and doesn't write anything
8523       *
8524       * @param mixed $data string or array, must not be NULL
8525       * @return string Always returns ''
8526       */
8527      public function write_setting($data) {
8528          // Do not write any setting.
8529          return '';
8530      }
8531  
8532      /**
8533       * Search to find if Query is related to content bank plugin
8534       *
8535       * @param string $query The string to search for
8536       * @return bool true for related false for not
8537       */
8538      public function is_related($query) {
8539          if (parent::is_related($query)) {
8540              return true;
8541          }
8542          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8543          foreach ($types as $type) {
8544              if (strpos($type->component, $query) !== false ||
8545                  strpos(core_text::strtolower($type->displayname), $query) !== false) {
8546                  return true;
8547              }
8548          }
8549          return false;
8550      }
8551  
8552      /**
8553       * Return XHTML to display control
8554       *
8555       * @param mixed $data Unused
8556       * @param string $query
8557       * @return string highlight
8558       */
8559      public function output_html($data, $query='') {
8560          global $CFG, $OUTPUT;
8561          $return = '';
8562  
8563          $types = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
8564          $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'order', 'up', 'down', 'default'));
8565          $txt->uninstall = get_string('uninstallplugin', 'core_admin');
8566  
8567          $table = new html_table();
8568          $table->head  = array($txt->name, $txt->enable, $txt->order, $txt->settings, $txt->uninstall);
8569          $table->align = array('left', 'center', 'center', 'center', 'center');
8570          $table->attributes['class'] = 'managecontentbanktable generaltable admintable';
8571          $table->data  = array();
8572          $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
8573  
8574          $totalenabled = 0;
8575          $count = 0;
8576          foreach ($types as $type) {
8577              if ($type->is_enabled() && $type->is_installed_and_upgraded()) {
8578                  $totalenabled++;
8579              }
8580          }
8581  
8582          foreach ($types as $type) {
8583              $url = new moodle_url('/admin/contentbank.php',
8584                  array('sesskey' => sesskey(), 'name' => $type->name));
8585  
8586              $class = '';
8587              $strtypename = $type->displayname;
8588              if ($type->is_enabled()) {
8589                  $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
8590                      $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
8591              } else {
8592                  $class = 'dimmed_text';
8593                  $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
8594                      $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
8595              }
8596  
8597              $updown = '';
8598              if ($count) {
8599                  $updown .= html_writer::link($url->out(false, array('action' => 'up')),
8600                          $OUTPUT->pix_icon('t/up', $txt->up, 'moodle', array('class' => 'iconsmall'))). '';
8601              } else {
8602                  $updown .= $spacer;
8603              }
8604              if ($count < count($types) - 1) {
8605                  $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
8606                          $OUTPUT->pix_icon('t/down', $txt->down, 'moodle', array('class' => 'iconsmall')));
8607              } else {
8608                  $updown .= $spacer;
8609              }
8610  
8611              $settings = '';
8612              if ($type->get_settings_url()) {
8613                  $settings = html_writer::link($type->get_settings_url(), $txt->settings);
8614              }
8615  
8616              $uninstall = '';
8617              if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('contenttype_'.$type->name, 'manage')) {
8618                  $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
8619              }
8620  
8621              $row = new html_table_row(array($strtypename, $hideshow, $updown, $settings, $uninstall));
8622              if ($class) {
8623                  $row->attributes['class'] = $class;
8624              }
8625              $table->data[] = $row;
8626              $count++;
8627          }
8628          $return .= html_writer::table($table);
8629          return highlight($query, $return);
8630      }
8631  }
8632  
8633  /**
8634   * Initialise admin page - this function does require login and permission
8635   * checks specified in page definition.
8636   *
8637   * This function must be called on each admin page before other code.
8638   *
8639   * @global moodle_page $PAGE
8640   *
8641   * @param string $section name of page
8642   * @param string $extrabutton extra HTML that is added after the blocks editing on/off button.
8643   * @param array $extraurlparams an array paramname => paramvalue, or parameters that need to be
8644   *      added to the turn blocks editing on/off form, so this page reloads correctly.
8645   * @param string $actualurl if the actual page being viewed is not the normal one for this
8646   *      page (e.g. admin/roles/allow.php, instead of admin/roles/manage.php, you can pass the alternate URL here.
8647   * @param array $options Additional options that can be specified for page setup.
8648   *      pagelayout - This option can be used to set a specific pagelyaout, admin is default.
8649   */
8650  function admin_externalpage_setup($section, $extrabutton = '', array $extraurlparams = null, $actualurl = '', array $options = array()) {
8651      global $CFG, $PAGE, $USER, $SITE, $OUTPUT;
8652  
8653      $PAGE->set_context(null); // hack - set context to something, by default to system context
8654  
8655      $site = get_site();
8656      require_login(null, false);
8657  
8658      if (!empty($options['pagelayout'])) {
8659          // A specific page layout has been requested.
8660          $PAGE->set_pagelayout($options['pagelayout']);
8661      } else if ($section === 'upgradesettings') {
8662          $PAGE->set_pagelayout('maintenance');
8663      } else {
8664          $PAGE->set_pagelayout('admin');
8665      }
8666  
8667      $adminroot = admin_get_root(false, false); // settings not required for external pages
8668      $extpage = $adminroot->locate($section, true);
8669  
8670      if (empty($extpage) or !($extpage instanceof admin_externalpage)) {
8671          // The requested section isn't in the admin tree
8672          // It could be because the user has inadequate capapbilities or because the section doesn't exist
8673          if (!has_capability('moodle/site:config', context_system::instance())) {
8674              // The requested section could depend on a different capability
8675              // but most likely the user has inadequate capabilities
8676              print_error('accessdenied', 'admin');
8677          } else {
8678              print_error('sectionerror', 'admin', "$CFG->wwwroot/$CFG->admin/");
8679          }
8680      }
8681  
8682      // this eliminates our need to authenticate on the actual pages
8683      if (!$extpage->check_access()) {
8684          print_error('accessdenied', 'admin');
8685          die;
8686      }
8687  
8688      navigation_node::require_admin_tree();
8689  
8690      // $PAGE->set_extra_button($extrabutton); TODO
8691  
8692      if (!$actualurl) {
8693          $actualurl = $extpage->url;
8694      }
8695  
8696      $PAGE->set_url($actualurl, $extraurlparams);
8697      if (strpos($PAGE->pagetype, 'admin-') !== 0) {
8698          $PAGE->set_pagetype('admin-' . $PAGE->pagetype);
8699      }
8700  
8701      if (empty($SITE->fullname) || empty($SITE->shortname)) {
8702          // During initial install.
8703          $strinstallation = get_string('installation', 'install');
8704          $strsettings = get_string('settings');
8705          $PAGE->navbar->add($strsettings);
8706          $PAGE->set_title($strinstallation);
8707          $PAGE->set_heading($strinstallation);
8708          $PAGE->set_cacheable(false);
8709          return;
8710      }
8711  
8712      // Locate the current item on the navigation and make it active when found.
8713      $path = $extpage->path;
8714      $node = $PAGE->settingsnav;
8715      while ($node && count($path) > 0) {
8716          $node = $node->get(array_pop($path));
8717      }
8718      if ($node) {
8719          $node->make_active();
8720      }
8721  
8722      // Normal case.
8723      $adminediting = optional_param('adminedit', -1, PARAM_BOOL);
8724      if ($PAGE->user_allowed_editing() && $adminediting != -1) {
8725          $USER->editing = $adminediting;
8726      }
8727  
8728      $visiblepathtosection = array_reverse($extpage->visiblepath);
8729  
8730      if ($PAGE->user_allowed_editing()) {
8731          if ($PAGE->user_is_editing()) {
8732              $caption = get_string('blockseditoff');
8733              $url = new moodle_url($PAGE->url, array('adminedit'=>'0', 'sesskey'=>sesskey()));
8734          } else {
8735              $caption = get_string('blocksediton');
8736              $url = new moodle_url($PAGE->url, array('adminedit'=>'1', 'sesskey'=>sesskey()));
8737          }
8738          $PAGE->set_button($OUTPUT->single_button($url, $caption, 'get'));
8739      }
8740  
8741      $PAGE->set_title("$SITE->shortname: " . implode(": ", $visiblepathtosection));
8742      $PAGE->set_heading($SITE->fullname);
8743  
8744      // prevent caching in nav block
8745      $PAGE->navigation->clear_cache();
8746  }
8747  
8748  /**
8749   * Returns the reference to admin tree root
8750   *
8751   * @return object admin_root object
8752   */
8753  function admin_get_root($reload=false, $requirefulltree=true) {
8754      global $CFG, $DB, $OUTPUT, $ADMIN;
8755  
8756      if (is_null($ADMIN)) {
8757      // create the admin tree!
8758          $ADMIN = new admin_root($requirefulltree);
8759      }
8760  
8761      if ($reload or ($requirefulltree and !$ADMIN->fulltree)) {
8762          $ADMIN->purge_children($requirefulltree);
8763      }
8764  
8765      if (!$ADMIN->loaded) {
8766      // we process this file first to create categories first and in correct order
8767          require($CFG->dirroot.'/'.$CFG->admin.'/settings/top.php');
8768  
8769          // now we process all other files in admin/settings to build the admin tree
8770          foreach (glob($CFG->dirroot.'/'.$CFG->admin.'/settings/*.php') as $file) {
8771              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/top.php') {
8772                  continue;
8773              }
8774              if ($file == $CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php') {
8775              // plugins are loaded last - they may insert pages anywhere
8776                  continue;
8777              }
8778              require($file);
8779          }
8780          require($CFG->dirroot.'/'.$CFG->admin.'/settings/plugins.php');
8781  
8782          $ADMIN->loaded = true;
8783      }
8784  
8785      return $ADMIN;
8786  }
8787  
8788  /// settings utility functions
8789  
8790  /**
8791   * This function applies default settings.
8792   * Because setting the defaults of some settings can enable other settings,
8793   * this function is called recursively until no more new settings are found.
8794   *
8795   * @param object $node, NULL means complete tree, null by default
8796   * @param bool $unconditional if true overrides all values with defaults, true by default
8797   * @param array $admindefaultsettings default admin settings to apply. Used recursively
8798   * @param array $settingsoutput The names and values of the changed settings. Used recursively
8799   * @return array $settingsoutput The names and values of the changed settings
8800   */
8801  function admin_apply_default_settings($node=null, $unconditional=true, $admindefaultsettings=array(), $settingsoutput=array()) {
8802      $counter = 0;
8803  
8804      if (is_null($node)) {
8805          core_plugin_manager::reset_caches();
8806          $node = admin_get_root(true, true);
8807          $counter = count($settingsoutput);
8808      }
8809  
8810      if ($node instanceof admin_category) {
8811          $entries = array_keys($node->children);
8812          foreach ($entries as $entry) {
8813              $settingsoutput = admin_apply_default_settings(
8814                      $node->children[$entry], $unconditional, $admindefaultsettings, $settingsoutput
8815                      );
8816          }
8817  
8818      } else if ($node instanceof admin_settingpage) {
8819          foreach ($node->settings as $setting) {
8820              if (!$unconditional && !is_null($setting->get_setting())) {
8821                  // Do not override existing defaults.
8822                  continue;
8823              }
8824              $defaultsetting = $setting->get_defaultsetting();
8825              if (is_null($defaultsetting)) {
8826                  // No value yet - default maybe applied after admin user creation or in upgradesettings.
8827                  continue;
8828              }
8829  
8830              $settingname = $node->name . '_' . $setting->name; // Get a unique name for the setting.
8831  
8832              if (!array_key_exists($settingname, $admindefaultsettings)) {  // Only update a setting if not already processed.
8833                  $admindefaultsettings[$settingname] = $settingname;
8834                  $settingsoutput[$settingname] = $defaultsetting;
8835  
8836                  // Set the default for this setting.
8837                  $setting->write_setting($defaultsetting);
8838                  $setting->write_setting_flags(null);
8839              } else {
8840                  unset($admindefaultsettings[$settingname]); // Remove processed settings.
8841              }
8842          }
8843      }
8844  
8845      // Call this function recursively until all settings are processed.
8846      if (($node instanceof admin_root) && ($counter != count($settingsoutput))) {
8847          $settingsoutput = admin_apply_default_settings(null, $unconditional, $admindefaultsettings, $settingsoutput);
8848      }
8849      // Just in case somebody modifies the list of active plugins directly.
8850      core_plugin_manager::reset_caches();
8851  
8852      return $settingsoutput;
8853  }
8854  
8855  /**
8856   * Store changed settings, this function updates the errors variable in $ADMIN
8857   *
8858   * @param object $formdata from form
8859   * @return int number of changed settings
8860   */
8861  function admin_write_settings($formdata) {
8862      global $CFG, $SITE, $DB;
8863  
8864      $olddbsessions = !empty($CFG->dbsessions);
8865      $formdata = (array)$formdata;
8866  
8867      $data = array();
8868      foreach ($formdata as $fullname=>$value) {
8869          if (strpos($fullname, 's_') !== 0) {
8870              continue; // not a config value
8871          }
8872          $data[$fullname] = $value;
8873      }
8874  
8875      $adminroot = admin_get_root();
8876      $settings = admin_find_write_settings($adminroot, $data);
8877  
8878      $count = 0;
8879      foreach ($settings as $fullname=>$setting) {
8880          /** @var $setting admin_setting */
8881          $original = $setting->get_setting();
8882          $error = $setting->write_setting($data[$fullname]);
8883          if ($error !== '') {
8884              $adminroot->errors[$fullname] = new stdClass();
8885              $adminroot->errors[$fullname]->data  = $data[$fullname];
8886              $adminroot->errors[$fullname]->id    = $setting->get_id();
8887              $adminroot->errors[$fullname]->error = $error;
8888          } else {
8889              $setting->write_setting_flags($data);
8890          }
8891          if ($setting->post_write_settings($original)) {
8892              $count++;
8893          }
8894      }
8895  
8896      if ($olddbsessions != !empty($CFG->dbsessions)) {
8897          require_logout();
8898      }
8899  
8900      // Now update $SITE - just update the fields, in case other people have a
8901      // a reference to it (e.g. $PAGE, $COURSE).
8902      $newsite = $DB->get_record('course', array('id'=>$SITE->id));
8903      foreach (get_object_vars($newsite) as $field => $value) {
8904          $SITE->$field = $value;
8905      }
8906  
8907      // now reload all settings - some of them might depend on the changed
8908      admin_get_root(true);
8909      return $count;
8910  }
8911  
8912  /**
8913   * Internal recursive function - finds all settings from submitted form
8914   *
8915   * @param object $node Instance of admin_category, or admin_settingpage
8916   * @param array $data
8917   * @return array
8918   */
8919  function admin_find_write_settings($node, $data) {
8920      $return = array();
8921  
8922      if (empty($data)) {
8923          return $return;
8924      }
8925  
8926      if ($node instanceof admin_category) {
8927          if ($node->check_access()) {
8928              $entries = array_keys($node->children);
8929              foreach ($entries as $entry) {
8930                  $return = array_merge($return, admin_find_write_settings($node->children[$entry], $data));
8931              }
8932          }
8933  
8934      } else if ($node instanceof admin_settingpage) {
8935          if ($node->check_access()) {
8936              foreach ($node->settings as $setting) {
8937                  $fullname = $setting->get_full_name();
8938                  if (array_key_exists($fullname, $data)) {
8939                      $return[$fullname] = $setting;
8940                  }
8941              }
8942          }
8943  
8944      }
8945  
8946      return $return;
8947  }
8948  
8949  /**
8950   * Internal function - prints the search results
8951   *
8952   * @param string $query String to search for
8953   * @return string empty or XHTML
8954   */
8955  function admin_search_settings_html($query) {
8956      global $CFG, $OUTPUT, $PAGE;
8957  
8958      if (core_text::strlen($query) < 2) {
8959          return '';
8960      }
8961      $query = core_text::strtolower($query);
8962  
8963      $adminroot = admin_get_root();
8964      $findings = $adminroot->search($query);
8965      $savebutton = false;
8966  
8967      $tpldata = (object) [
8968          'actionurl' => $PAGE->url->out(false),
8969          'results' => [],
8970          'sesskey' => sesskey(),
8971      ];
8972  
8973      foreach ($findings as $found) {
8974          $page     = $found->page;
8975          $settings = $found->settings;
8976          if ($page->is_hidden()) {
8977          // hidden pages are not displayed in search results
8978              continue;
8979          }
8980  
8981          $heading = highlight($query, $page->visiblename);
8982          $headingurl = null;
8983          if ($page instanceof admin_externalpage) {
8984              $headingurl = new moodle_url($page->url);
8985          } else if ($page instanceof admin_settingpage) {
8986              $headingurl = new moodle_url('/admin/settings.php', ['section' => $page->name]);
8987          } else {
8988              continue;
8989          }
8990  
8991          // Locate the page in the admin root and populate its visiblepath attribute.
8992          $path = array();
8993          $located = $adminroot->locate($page->name, true);
8994          if ($located) {
8995              foreach ($located->visiblepath as $pathitem) {
8996                  array_unshift($path, (string) $pathitem);
8997              }
8998          }
8999  
9000          $sectionsettings = [];
9001          if (!empty($settings)) {
9002              foreach ($settings as $setting) {
9003                  if (empty($setting->nosave)) {
9004                      $savebutton = true;
9005                  }
9006                  $fullname = $setting->get_full_name();
9007                  if (array_key_exists($fullname, $adminroot->errors)) {
9008                      $data = $adminroot->errors[$fullname]->data;
9009                  } else {
9010                      $data = $setting->get_setting();
9011                  // do not use defaults if settings not available - upgradesettings handles the defaults!
9012                  }
9013                  $sectionsettings[] = $setting->output_html($data, $query);
9014              }
9015          }
9016  
9017          $tpldata->results[] = (object) [
9018              'title' => $heading,
9019              'path' => $path,
9020              'url' => $headingurl->out(false),
9021              'settings' => $sectionsettings
9022          ];
9023      }
9024  
9025      $tpldata->showsave = $savebutton;
9026      $tpldata->hasresults = !empty($tpldata->results);
9027  
9028      return $OUTPUT->render_from_template('core_admin/settings_search_results', $tpldata);
9029  }
9030  
9031  /**
9032   * Internal function - returns arrays of html pages with uninitialised settings
9033   *
9034   * @param object $node Instance of admin_category or admin_settingpage
9035   * @return array
9036   */
9037  function admin_output_new_settings_by_page($node) {
9038      global $OUTPUT;
9039      $return = array();
9040  
9041      if ($node instanceof admin_category) {
9042          $entries = array_keys($node->children);
9043          foreach ($entries as $entry) {
9044              $return += admin_output_new_settings_by_page($node->children[$entry]);
9045          }
9046  
9047      } else if ($node instanceof admin_settingpage) {
9048              $newsettings = array();
9049              foreach ($node->settings as $setting) {
9050                  if (is_null($setting->get_setting())) {
9051                      $newsettings[] = $setting;
9052                  }
9053              }
9054              if (count($newsettings) > 0) {
9055                  $adminroot = admin_get_root();
9056                  $page = $OUTPUT->heading(get_string('upgradesettings','admin').' - '.$node->visiblename, 2, 'main');
9057                  $page .= '<fieldset class="adminsettings">'."\n";
9058                  foreach ($newsettings as $setting) {
9059                      $fullname = $setting->get_full_name();
9060                      if (array_key_exists($fullname, $adminroot->errors)) {
9061                          $data = $adminroot->errors[$fullname]->data;
9062                      } else {
9063                          $data = $setting->get_setting();
9064                          if (is_null($data)) {
9065                              $data = $setting->get_defaultsetting();
9066                          }
9067                      }
9068                      $page .= '<div class="clearer"><!-- --></div>'."\n";
9069                      $page .= $setting->output_html($data);
9070                  }
9071                  $page .= '</fieldset>';
9072                  $return[$node->name] = $page;
9073              }
9074          }
9075  
9076      return $return;
9077  }
9078  
9079  /**
9080   * Format admin settings
9081   *
9082   * @param object $setting
9083   * @param string $title label element
9084   * @param string $form form fragment, html code - not highlighted automatically
9085   * @param string $description
9086   * @param mixed $label link label to id, true by default or string being the label to connect it to
9087   * @param string $warning warning text
9088   * @param sting $defaultinfo defaults info, null means nothing, '' is converted to "Empty" string, defaults to null
9089   * @param string $query search query to be highlighted
9090   * @return string XHTML
9091   */
9092  function format_admin_setting($setting, $title='', $form='', $description='', $label=true, $warning='', $defaultinfo=NULL, $query='') {
9093      global $CFG, $OUTPUT;
9094  
9095      $context = (object) [
9096          'name' => empty($setting->plugin) ? $setting->name : "$setting->plugin | $setting->name",
9097          'fullname' => $setting->get_full_name(),
9098      ];
9099  
9100      // Sometimes the id is not id_s_name, but id_s_name_m or something, and this does not validate.
9101      if ($label === true) {
9102          $context->labelfor = $setting->get_id();
9103      } else if ($label === false) {
9104          $context->labelfor = '';
9105      } else {
9106          $context->labelfor = $label;
9107      }
9108  
9109      $form .= $setting->output_setting_flags();
9110  
9111      $context->warning = $warning;
9112      $context->override = '';
9113      if (empty($setting->plugin)) {
9114          if (array_key_exists($setting->name, $CFG->config_php_settings)) {
9115              $context->override = get_string('configoverride', 'admin');
9116          }
9117      } else {
9118          if (array_key_exists($setting->plugin, $CFG->forced_plugin_settings) and array_key_exists($setting->name, $CFG->forced_plugin_settings[$setting->plugin])) {
9119              $context->override = get_string('configoverride', 'admin');
9120          }
9121      }
9122  
9123      $defaults = array();
9124      if (!is_null($defaultinfo)) {
9125          if ($defaultinfo === '') {
9126              $defaultinfo = get_string('emptysettingvalue', 'admin');
9127          }
9128          $defaults[] = $defaultinfo;
9129      }
9130  
9131      $context->default = null;
9132      $setting->get_setting_flag_defaults($defaults);
9133      if (!empty($defaults)) {
9134          $defaultinfo = implode(', ', $defaults);
9135          $defaultinfo = highlight($query, nl2br(s($defaultinfo)));
9136          $context->default = get_string('defaultsettinginfo', 'admin', $defaultinfo);
9137      }
9138  
9139  
9140      $context->error = '';
9141      $adminroot = admin_get_root();
9142      if (array_key_exists($context->fullname, $adminroot->errors)) {
9143          $context->error = $adminroot->errors[$context->fullname]->error;
9144      }
9145  
9146      if ($dependenton = $setting->get_dependent_on()) {
9147          $context->dependenton = get_string('settingdependenton', 'admin', implode(', ', $dependenton));
9148      }
9149  
9150      $context->id = 'admin-' . $setting->name;
9151      $context->title = highlightfast($query, $title);
9152      $context->name = highlightfast($query, $context->name);
9153      $context->description = highlight($query, markdown_to_html($description));
9154      $context->element = $form;
9155      $context->forceltr = $setting->get_force_ltr();
9156      $context->customcontrol = $setting->has_custom_form_control();
9157  
9158      return $OUTPUT->render_from_template('core_admin/setting', $context);
9159  }
9160  
9161  /**
9162   * Based on find_new_settings{@link ()}  in upgradesettings.php
9163   * Looks to find any admin settings that have not been initialized. Returns 1 if it finds any.
9164   *
9165   * @param object $node Instance of admin_category, or admin_settingpage
9166   * @return boolean true if any settings haven't been initialised, false if they all have
9167   */
9168  function any_new_admin_settings($node) {
9169  
9170      if ($node instanceof admin_category) {
9171          $entries = array_keys($node->children);
9172          foreach ($entries as $entry) {
9173              if (any_new_admin_settings($node->children[$entry])) {
9174                  return true;
9175              }
9176          }
9177  
9178      } else if ($node instanceof admin_settingpage) {
9179              foreach ($node->settings as $setting) {
9180                  if ($setting->get_setting() === NULL) {
9181                      return true;
9182                  }
9183              }
9184          }
9185  
9186      return false;
9187  }
9188  
9189  /**
9190   * Given a table and optionally a column name should replaces be done?
9191   *
9192   * @param string $table name
9193   * @param string $column name
9194   * @return bool success or fail
9195   */
9196  function db_should_replace($table, $column = '', $additionalskiptables = ''): bool {
9197  
9198      // TODO: this is horrible hack, we should have a hook and each plugin should be responsible for proper replacing...
9199      $skiptables = ['config', 'config_plugins', 'filter_config', 'sessions',
9200          'events_queue', 'repository_instance_config', 'block_instances', 'files'];
9201  
9202      // Additional skip tables.
9203      if (!empty($additionalskiptables)) {
9204          $skiptables = array_merge($skiptables, explode(',', str_replace(' ', '',  $additionalskiptables)));
9205      }
9206  
9207      // Don't process these.
9208      if (in_array($table, $skiptables)) {
9209          return false;
9210      }
9211  
9212      // To be safe never replace inside a table that looks related to logging.
9213      if (preg_match('/(^|_)logs?($|_)/', $table)) {
9214          return false;
9215      }
9216  
9217      // Do column based exclusions.
9218      if (!empty($column)) {
9219          // Don't touch anything that looks like a hash.
9220          if (preg_match('/hash$/', $column)) {
9221              return false;
9222          }
9223      }
9224  
9225      return true;
9226  }
9227  
9228  /**
9229   * Moved from admin/replace.php so that we can use this in cron
9230   *
9231   * @param string $search string to look for
9232   * @param string $replace string to replace
9233   * @return bool success or fail
9234   */
9235  function db_replace($search, $replace, $additionalskiptables = '') {
9236      global $DB, $CFG, $OUTPUT;
9237  
9238      // Turn off time limits, sometimes upgrades can be slow.
9239      core_php_time_limit::raise();
9240  
9241      if (!$tables = $DB->get_tables() ) {    // No tables yet at all.
9242          return false;
9243      }
9244      foreach ($tables as $table) {
9245  
9246          if (!db_should_replace($table, '', $additionalskiptables)) {
9247              continue;
9248          }
9249  
9250          if ($columns = $DB->get_columns($table)) {
9251              $DB->set_debug(true);
9252              foreach ($columns as $column) {
9253                  if (!db_should_replace($table, $column->name)) {
9254                      continue;
9255                  }
9256                  $DB->replace_all_text($table, $column, $search, $replace);
9257              }
9258              $DB->set_debug(false);
9259          }
9260      }
9261  
9262      // delete modinfo caches
9263      rebuild_course_cache(0, true);
9264  
9265      // TODO: we should ask all plugins to do the search&replace, for now let's do only blocks...
9266      $blocks = core_component::get_plugin_list('block');
9267      foreach ($blocks as $blockname=>$fullblock) {
9268          if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
9269              continue;
9270          }
9271  
9272          if (!is_readable($fullblock.'/lib.php')) {
9273              continue;
9274          }
9275  
9276          $function = 'block_'.$blockname.'_global_db_replace';
9277          include_once($fullblock.'/lib.php');
9278          if (!function_exists($function)) {
9279              continue;
9280          }
9281  
9282          echo $OUTPUT->notification("Replacing in $blockname blocks...", 'notifysuccess');
9283          $function($search, $replace);
9284          echo $OUTPUT->notification("...finished", 'notifysuccess');
9285      }
9286  
9287      // Trigger an event.
9288      $eventargs = [
9289          'context' => context_system::instance(),
9290          'other' => [
9291              'search' => $search,
9292              'replace' => $replace
9293          ]
9294      ];
9295      $event = \core\event\database_text_field_content_replaced::create($eventargs);
9296      $event->trigger();
9297  
9298      purge_all_caches();
9299  
9300      return true;
9301  }
9302  
9303  /**
9304   * Manage repository settings
9305   *
9306   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
9307   */
9308  class admin_setting_managerepository extends admin_setting {
9309  /** @var string */
9310      private $baseurl;
9311  
9312      /**
9313       * calls parent::__construct with specific arguments
9314       */
9315      public function __construct() {
9316          global $CFG;
9317          parent::__construct('managerepository', get_string('manage', 'repository'), '', '');
9318          $this->baseurl = $CFG->wwwroot . '/' . $CFG->admin . '/repository.php?sesskey=' . sesskey();
9319      }
9320  
9321      /**
9322       * Always returns true, does nothing
9323       *
9324       * @return true
9325       */
9326      public function get_setting() {
9327          return true;
9328      }
9329  
9330      /**
9331       * Always returns true does nothing
9332       *
9333       * @return true
9334       */
9335      public function get_defaultsetting() {
9336          return true;
9337      }
9338  
9339      /**
9340       * Always returns s_managerepository
9341       *
9342       * @return string Always return 's_managerepository'
9343       */
9344      public function get_full_name() {
9345          return 's_managerepository';
9346      }
9347  
9348      /**
9349       * Always returns '' doesn't do anything
9350       */
9351      public function write_setting($data) {
9352          $url = $this->baseurl . '&amp;new=' . $data;
9353          return '';
9354      // TODO
9355      // Should not use redirect and exit here
9356      // Find a better way to do this.
9357      // redirect($url);
9358      // exit;
9359      }
9360  
9361      /**
9362       * Searches repository plugins for one that matches $query
9363       *
9364       * @param string $query The string to search for
9365       * @return bool true if found, false if not
9366       */
9367      public function is_related($query) {
9368          if (parent::is_related($query)) {
9369              return true;
9370          }
9371  
9372          $repositories= core_component::get_plugin_list('repository');
9373          foreach ($repositories as $p => $dir) {
9374              if (strpos($p, $query) !== false) {
9375                  return true;
9376              }
9377          }
9378          foreach (repository::get_types() as $instance) {
9379              $title = $instance->get_typename();
9380              if (strpos(core_text::strtolower($title), $query) !== false) {
9381                  return true;
9382              }
9383          }
9384          return false;
9385      }
9386  
9387      /**
9388       * Helper function that generates a moodle_url object
9389       * relevant to the repository
9390       */
9391  
9392      function repository_action_url($repository) {
9393          return new moodle_url($this->baseurl, array('sesskey'=>sesskey(), 'repos'=>$repository));
9394      }
9395  
9396      /**
9397       * Builds XHTML to display the control
9398       *
9399       * @param string $data Unused
9400       * @param string $query
9401       * @return string XHTML
9402       */
9403      public function output_html($data, $query='') {
9404          global $CFG, $USER, $OUTPUT;
9405  
9406          // Get strings that are used
9407          $strshow = get_string('on', 'repository');
9408          $strhide = get_string('off', 'repository');
9409          $strdelete = get_string('disabled', 'repository');
9410  
9411          $actionchoicesforexisting = array(
9412              'show' => $strshow,
9413              'hide' => $strhide,
9414              'delete' => $strdelete
9415          );
9416  
9417          $actionchoicesfornew = array(
9418              'newon' => $strshow,
9419              'newoff' => $strhide,
9420              'delete' => $strdelete
9421          );
9422  
9423          $return = '';
9424          $return .= $OUTPUT->box_start('generalbox');
9425  
9426          // Set strings that are used multiple times
9427          $settingsstr = get_string('settings');
9428          $disablestr = get_string('disable');
9429  
9430          // Table to list plug-ins
9431          $table = new html_table();
9432          $table->head = array(get_string('name'), get_string('isactive', 'repository'), get_string('order'), $settingsstr);
9433          $table->align = array('left', 'center', 'center', 'center', 'center');
9434          $table->data = array();
9435  
9436          // Get list of used plug-ins
9437          $repositorytypes = repository::get_types();
9438          if (!empty($repositorytypes)) {
9439              // Array to store plugins being used
9440              $alreadyplugins = array();
9441              $totalrepositorytypes = count($repositorytypes);
9442              $updowncount = 1;
9443              foreach ($repositorytypes as $i) {
9444                  $settings = '';
9445                  $typename = $i->get_typename();
9446                  // Display edit link only if you can config the type or if it has multiple instances (e.g. has instance config)
9447                  $typeoptionnames = repository::static_function($typename, 'get_type_option_names');
9448                  $instanceoptionnames = repository::static_function($typename, 'get_instance_option_names');
9449  
9450                  if (!empty($typeoptionnames) || !empty($instanceoptionnames)) {
9451                      // Calculate number of instances in order to display them for the Moodle administrator
9452                      if (!empty($instanceoptionnames)) {
9453                          $params = array();
9454                          $params['context'] = array(context_system::instance());
9455                          $params['onlyvisible'] = false;
9456                          $params['type'] = $typename;
9457                          $admininstancenumber = count(repository::static_function($typename, 'get_instances', $params));
9458                          // site instances
9459                          $admininstancenumbertext = get_string('instancesforsite', 'repository', $admininstancenumber);
9460                          $params['context'] = array();
9461                          $instances = repository::static_function($typename, 'get_instances', $params);
9462                          $courseinstances = array();
9463                          $userinstances = array();
9464  
9465                          foreach ($instances as $instance) {
9466                              $repocontext = context::instance_by_id($instance->instance->contextid);
9467                              if ($repocontext->contextlevel == CONTEXT_COURSE) {
9468                                  $courseinstances[] = $instance;
9469                              } else if ($repocontext->contextlevel == CONTEXT_USER) {
9470                                  $userinstances[] = $instance;
9471                              }
9472                          }
9473                          // course instances
9474                          $instancenumber = count($courseinstances);
9475                          $courseinstancenumbertext = get_string('instancesforcourses', 'repository', $instancenumber);
9476  
9477                          // user private instances
9478                          $instancenumber =  count($userinstances);
9479                          $userinstancenumbertext = get_string('instancesforusers', 'repository', $instancenumber);
9480                      } else {
9481                          $admininstancenumbertext = "";
9482                          $courseinstancenumbertext = "";
9483                          $userinstancenumbertext = "";
9484                      }
9485  
9486                      $settings .= '<a href="' . $this->baseurl . '&amp;action=edit&amp;repos=' . $typename . '">' . $settingsstr .'</a>';
9487  
9488                      $settings .= $OUTPUT->container_start('mdl-left');
9489                      $settings .= '<br/>';
9490                      $settings .= $admininstancenumbertext;
9491                      $settings .= '<br/>';
9492                      $settings .= $courseinstancenumbertext;
9493                      $settings .= '<br/>';
9494                      $settings .= $userinstancenumbertext;
9495                      $settings .= $OUTPUT->container_end();
9496                  }
9497                  // Get the current visibility
9498                  if ($i->get_visible()) {
9499                      $currentaction = 'show';
9500                  } else {
9501                      $currentaction = 'hide';
9502                  }
9503  
9504                  $select = new single_select($this->repository_action_url($typename, 'repos'), 'action', $actionchoicesforexisting, $currentaction, null, 'applyto' . basename($typename));
9505  
9506                  // Display up/down link
9507                  $updown = '';
9508                  // Should be done with CSS instead.
9509                  $spacer = $OUTPUT->spacer(array('height' => 15, 'width' => 15, 'class' => 'smallicon'));
9510  
9511                  if ($updowncount > 1) {
9512                      $updown .= "<a href=\"$this->baseurl&amp;action=moveup&amp;repos=".$typename."\">";
9513                      $updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . '</a>&nbsp;';
9514                  }
9515                  else {
9516                      $updown .= $spacer;
9517                  }
9518                  if ($updowncount < $totalrepositorytypes) {
9519                      $updown .= "<a href=\"$this->baseurl&amp;action=movedown&amp;repos=".$typename."\">";
9520                      $updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . '</a>&nbsp;';
9521                  }
9522                  else {
9523                      $updown .= $spacer;
9524                  }
9525  
9526                  $updowncount++;
9527  
9528                  $table->data[] = array($i->get_readablename(), $OUTPUT->render($select), $updown, $settings);
9529  
9530                  if (!in_array($typename, $alreadyplugins)) {
9531                      $alreadyplugins[] = $typename;
9532                  }
9533              }
9534          }
9535  
9536          // Get all the plugins that exist on disk
9537          $plugins = core_component::get_plugin_list('repository');
9538          if (!empty($plugins)) {
9539              foreach ($plugins as $plugin => $dir) {
9540                  // Check that it has not already been listed
9541                  if (!in_array($plugin, $alreadyplugins)) {
9542                      $select = new single_select($this->repository_action_url($plugin, 'repos'), 'action', $actionchoicesfornew, 'delete', null, 'applyto' . basename($plugin));
9543                      $table->data[] = array(get_string('pluginname', 'repository_'.$plugin), $OUTPUT->render($select), '', '');
9544                  }
9545              }
9546          }
9547  
9548          $return .= html_writer::table($table);
9549          $return .= $OUTPUT->box_end();
9550          return highlight($query, $return);
9551      }
9552  }
9553  
9554  /**
9555   * Special checkbox for enable mobile web service
9556   * If enable then we store the service id of the mobile service into config table
9557   * If disable then we unstore the service id from the config table
9558   */
9559  class admin_setting_enablemobileservice extends admin_setting_configcheckbox {
9560  
9561      /** @var boolean True means that the capability 'webservice/rest:use' is set for authenticated user role */
9562      private $restuse;
9563  
9564      /**
9565       * Return true if Authenticated user role has the capability 'webservice/rest:use', otherwise false.
9566       *
9567       * @return boolean
9568       */
9569      private function is_protocol_cap_allowed() {
9570          global $DB, $CFG;
9571  
9572          // If the $this->restuse variable is not set, it needs to be set.
9573          if (empty($this->restuse) and $this->restuse!==false) {
9574              $params = array();
9575              $params['permission'] = CAP_ALLOW;
9576              $params['roleid'] = $CFG->defaultuserroleid;
9577              $params['capability'] = 'webservice/rest:use';
9578              $this->restuse = $DB->record_exists('role_capabilities', $params);
9579          }
9580  
9581          return $this->restuse;
9582      }
9583  
9584      /**
9585       * Set the 'webservice/rest:use' to the Authenticated user role (allow or not)
9586       * @param type $status true to allow, false to not set
9587       */
9588      private function set_protocol_cap($status) {
9589          global $CFG;
9590          if ($status and !$this->is_protocol_cap_allowed()) {
9591              //need to allow the cap
9592              $permission = CAP_ALLOW;
9593              $assign = true;
9594          } else if (!$status and $this->is_protocol_cap_allowed()){
9595              //need to disallow the cap
9596              $permission = CAP_INHERIT;
9597              $assign = true;
9598          }
9599          if (!empty($assign)) {
9600              $systemcontext = context_system::instance();
9601              assign_capability('webservice/rest:use', $permission, $CFG->defaultuserroleid, $systemcontext->id, true);
9602          }
9603      }
9604  
9605      /**
9606       * Builds XHTML to display the control.
9607       * The main purpose of this overloading is to display a warning when https
9608       * is not supported by the server
9609       * @param string $data Unused
9610       * @param string $query
9611       * @return string XHTML
9612       */
9613      public function output_html($data, $query='') {
9614          global $OUTPUT;
9615          $html = parent::output_html($data, $query);
9616  
9617          if ((string)$data === $this->yes) {
9618              $notifications = tool_mobile\api::get_potential_config_issues(); // Safe to call, plugin available if we reach here.
9619              foreach ($notifications as $notification) {
9620                  $message = get_string($notification[0], $notification[1]);
9621                  $html .= $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING);
9622              }
9623          }
9624  
9625          return $html;
9626      }
9627  
9628      /**
9629       * Retrieves the current setting using the objects name
9630       *
9631       * @return string
9632       */
9633      public function get_setting() {
9634          global $CFG;
9635  
9636          // First check if is not set.
9637          $result = $this->config_read($this->name);
9638          if (is_null($result)) {
9639              return null;
9640          }
9641  
9642          // For install cli script, $CFG->defaultuserroleid is not set so return 0
9643          // Or if web services aren't enabled this can't be,
9644          if (empty($CFG->defaultuserroleid) || empty($CFG->enablewebservices)) {
9645              return 0;
9646          }
9647  
9648          require_once($CFG->dirroot . '/webservice/lib.php');
9649          $webservicemanager = new webservice();
9650          $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9651          if ($mobileservice->enabled and $this->is_protocol_cap_allowed()) {
9652              return $result;
9653          } else {
9654              return 0;
9655          }
9656      }
9657  
9658      /**
9659       * Save the selected setting
9660       *
9661       * @param string $data The selected site
9662       * @return string empty string or error message
9663       */
9664      public function write_setting($data) {
9665          global $DB, $CFG;
9666  
9667          //for install cli script, $CFG->defaultuserroleid is not set so do nothing
9668          if (empty($CFG->defaultuserroleid)) {
9669              return '';
9670          }
9671  
9672          $servicename = MOODLE_OFFICIAL_MOBILE_SERVICE;
9673  
9674          require_once($CFG->dirroot . '/webservice/lib.php');
9675          $webservicemanager = new webservice();
9676  
9677          $updateprotocol = false;
9678          if ((string)$data === $this->yes) {
9679               //code run when enable mobile web service
9680               //enable web service systeme if necessary
9681               set_config('enablewebservices', true);
9682  
9683               //enable mobile service
9684               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9685               $mobileservice->enabled = 1;
9686               $webservicemanager->update_external_service($mobileservice);
9687  
9688               // Enable REST server.
9689               $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
9690  
9691               if (!in_array('rest', $activeprotocols)) {
9692                   $activeprotocols[] = 'rest';
9693                   $updateprotocol = true;
9694               }
9695  
9696               if ($updateprotocol) {
9697                   set_config('webserviceprotocols', implode(',', $activeprotocols));
9698               }
9699  
9700               // Allow rest:use capability for authenticated user.
9701               $this->set_protocol_cap(true);
9702  
9703           } else {
9704               //disable web service system if no other services are enabled
9705               $otherenabledservices = $DB->get_records_select('external_services',
9706                       'enabled = :enabled AND (shortname != :shortname OR shortname IS NULL)', array('enabled' => 1,
9707                           'shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
9708               if (empty($otherenabledservices)) {
9709                   set_config('enablewebservices', false);
9710  
9711                   // Also disable REST server.
9712                   $activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
9713  
9714                   $protocolkey = array_search('rest', $activeprotocols);
9715                   if ($protocolkey !== false) {
9716                      unset($activeprotocols[$protocolkey]);
9717                      $updateprotocol = true;
9718                   }
9719  
9720                   if ($updateprotocol) {
9721                      set_config('webserviceprotocols', implode(',', $activeprotocols));
9722                   }
9723  
9724                   // Disallow rest:use capability for authenticated user.
9725                   $this->set_protocol_cap(false);
9726               }
9727  
9728               //disable the mobile service
9729               $mobileservice = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
9730               $mobileservice->enabled = 0;
9731               $webservicemanager->update_external_service($mobileservice);
9732           }
9733  
9734          return (parent::write_setting($data));
9735      }
9736  }
9737  
9738  /**
9739   * Special class for management of external services
9740   *
9741   * @author Petr Skoda (skodak)
9742   */
9743  class admin_setting_manageexternalservices extends admin_setting {
9744      /**
9745       * Calls parent::__construct with specific arguments
9746       */
9747      public function __construct() {
9748          $this->nosave = true;
9749          parent::__construct('webservicesui', get_string('externalservices', 'webservice'), '', '');
9750      }
9751  
9752      /**
9753       * Always returns true, does nothing
9754       *
9755       * @return true
9756       */
9757      public function get_setting() {
9758          return true;
9759      }
9760  
9761      /**
9762       * Always returns true, does nothing
9763       *
9764       * @return true
9765       */
9766      public function get_defaultsetting() {
9767          return true;
9768      }
9769  
9770      /**
9771       * Always returns '', does not write anything
9772       *
9773       * @return string Always returns ''
9774       */
9775      public function write_setting($data) {
9776      // do not write any setting
9777          return '';
9778      }
9779  
9780      /**
9781       * Checks if $query is one of the available external services
9782       *
9783       * @param string $query The string to search for
9784       * @return bool Returns true if found, false if not
9785       */
9786      public function is_related($query) {
9787          global $DB;
9788  
9789          if (parent::is_related($query)) {
9790              return true;
9791          }
9792  
9793          $services = $DB->get_records('external_services', array(), 'id, name');
9794          foreach ($services as $service) {
9795              if (strpos(core_text::strtolower($service->name), $query) !== false) {
9796                  return true;
9797              }
9798          }
9799          return false;
9800      }
9801  
9802      /**
9803       * Builds the XHTML to display the control
9804       *
9805       * @param string $data Unused
9806       * @param string $query
9807       * @return string
9808       */
9809      public function output_html($data, $query='') {
9810          global $CFG, $OUTPUT, $DB;
9811  
9812          // display strings
9813          $stradministration = get_string('administration');
9814          $stredit = get_string('edit');
9815          $strservice = get_string('externalservice', 'webservice');
9816          $strdelete = get_string('delete');
9817          $strplugin = get_string('plugin', 'admin');
9818          $stradd = get_string('add');
9819          $strfunctions = get_string('functions', 'webservice');
9820          $strusers = get_string('users');
9821          $strserviceusers = get_string('serviceusers', 'webservice');
9822  
9823          $esurl = "$CFG->wwwroot/$CFG->admin/webservice/service.php";
9824          $efurl = "$CFG->wwwroot/$CFG->admin/webservice/service_functions.php";
9825          $euurl = "$CFG->wwwroot/$CFG->admin/webservice/service_users.php";
9826  
9827          // built in services
9828           $services = $DB->get_records_select('external_services', 'component IS NOT NULL', null, 'name');
9829           $return = "";
9830           if (!empty($services)) {
9831              $return .= $OUTPUT->heading(get_string('servicesbuiltin', 'webservice'), 3, 'main');
9832  
9833  
9834  
9835              $table = new html_table();
9836              $table->head  = array($strservice, $strplugin, $strfunctions, $strusers, $stredit);
9837              $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9838              $table->id = 'builtinservices';
9839              $table->attributes['class'] = 'admintable externalservices generaltable';
9840              $table->data  = array();
9841  
9842              // iterate through auth plugins and add to the display table
9843              foreach ($services as $service) {
9844                  $name = $service->name;
9845  
9846                  // hide/show link
9847                  if ($service->enabled) {
9848                      $displayname = "<span>$name</span>";
9849                  } else {
9850                      $displayname = "<span class=\"dimmed_text\">$name</span>";
9851                  }
9852  
9853                  $plugin = $service->component;
9854  
9855                  $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9856  
9857                  if ($service->restrictedusers) {
9858                      $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9859                  } else {
9860                      $users = get_string('allusers', 'webservice');
9861                  }
9862  
9863                  $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9864  
9865                  // add a row to the table
9866                  $table->data[] = array($displayname, $plugin, $functions, $users, $edit);
9867              }
9868              $return .= html_writer::table($table);
9869          }
9870  
9871          // Custom services
9872          $return .= $OUTPUT->heading(get_string('servicescustom', 'webservice'), 3, 'main');
9873          $services = $DB->get_records_select('external_services', 'component IS NULL', null, 'name');
9874  
9875          $table = new html_table();
9876          $table->head  = array($strservice, $strdelete, $strfunctions, $strusers, $stredit);
9877          $table->colclasses = array('leftalign service', 'leftalign plugin', 'centeralign functions', 'centeralign users', 'centeralign ');
9878          $table->id = 'customservices';
9879          $table->attributes['class'] = 'admintable externalservices generaltable';
9880          $table->data  = array();
9881  
9882          // iterate through auth plugins and add to the display table
9883          foreach ($services as $service) {
9884              $name = $service->name;
9885  
9886              // hide/show link
9887              if ($service->enabled) {
9888                  $displayname = "<span>$name</span>";
9889              } else {
9890                  $displayname = "<span class=\"dimmed_text\">$name</span>";
9891              }
9892  
9893              // delete link
9894              $delete = "<a href=\"$esurl?action=delete&amp;sesskey=".sesskey()."&amp;id=$service->id\">$strdelete</a>";
9895  
9896              $functions = "<a href=\"$efurl?id=$service->id\">$strfunctions</a>";
9897  
9898              if ($service->restrictedusers) {
9899                  $users = "<a href=\"$euurl?id=$service->id\">$strserviceusers</a>";
9900              } else {
9901                  $users = get_string('allusers', 'webservice');
9902              }
9903  
9904              $edit = "<a href=\"$esurl?id=$service->id\">$stredit</a>";
9905  
9906              // add a row to the table
9907              $table->data[] = array($displayname, $delete, $functions, $users, $edit);
9908          }
9909          // add new custom service option
9910          $return .= html_writer::table($table);
9911  
9912          $return .= '<br />';
9913          // add a token to the table
9914          $return .= "<a href=\"$esurl?id=0\">$stradd</a>";
9915  
9916          return highlight($query, $return);
9917      }
9918  }
9919  
9920  /**
9921   * Special class for overview of external services
9922   *
9923   * @author Jerome Mouneyrac
9924   */
9925  class admin_setting_webservicesoverview extends admin_setting {
9926  
9927      /**
9928       * Calls parent::__construct with specific arguments
9929       */
9930      public function __construct() {
9931          $this->nosave = true;
9932          parent::__construct('webservicesoverviewui',
9933                          get_string('webservicesoverview', 'webservice'), '', '');
9934      }
9935  
9936      /**
9937       * Always returns true, does nothing
9938       *
9939       * @return true
9940       */
9941      public function get_setting() {
9942          return true;
9943      }
9944  
9945      /**
9946       * Always returns true, does nothing
9947       *
9948       * @return true
9949       */
9950      public function get_defaultsetting() {
9951          return true;
9952      }
9953  
9954      /**
9955       * Always returns '', does not write anything
9956       *
9957       * @return string Always returns ''
9958       */
9959      public function write_setting($data) {
9960          // do not write any setting
9961          return '';
9962      }
9963  
9964      /**
9965       * Builds the XHTML to display the control
9966       *
9967       * @param string $data Unused
9968       * @param string $query
9969       * @return string
9970       */
9971      public function output_html($data, $query='') {
9972          global $CFG, $OUTPUT;
9973  
9974          $return = "";
9975          $brtag = html_writer::empty_tag('br');
9976  
9977          /// One system controlling Moodle with Token
9978          $return .= $OUTPUT->heading(get_string('onesystemcontrolling', 'webservice'), 3, 'main');
9979          $table = new html_table();
9980          $table->head = array(get_string('step', 'webservice'), get_string('status'),
9981              get_string('description'));
9982          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
9983          $table->id = 'onesystemcontrol';
9984          $table->attributes['class'] = 'admintable wsoverview generaltable';
9985          $table->data = array();
9986  
9987          $return .= $brtag . get_string('onesystemcontrollingdescription', 'webservice')
9988                  . $brtag . $brtag;
9989  
9990          /// 1. Enable Web Services
9991          $row = array();
9992          $url = new moodle_url("/admin/search.php?query=enablewebservices");
9993          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
9994                          array('href' => $url));
9995          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
9996          if ($CFG->enablewebservices) {
9997              $status = get_string('yes');
9998          }
9999          $row[1] = $status;
10000          $row[2] = get_string('enablewsdescription', 'webservice');
10001          $table->data[] = $row;
10002  
10003          /// 2. Enable protocols
10004          $row = array();
10005          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10006          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10007                          array('href' => $url));
10008          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10009          //retrieve activated protocol
10010          $active_protocols = empty($CFG->webserviceprotocols) ?
10011                  array() : explode(',', $CFG->webserviceprotocols);
10012          if (!empty($active_protocols)) {
10013              $status = "";
10014              foreach ($active_protocols as $protocol) {
10015                  $status .= $protocol . $brtag;
10016              }
10017          }
10018          $row[1] = $status;
10019          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10020          $table->data[] = $row;
10021  
10022          /// 3. Create user account
10023          $row = array();
10024          $url = new moodle_url("/user/editadvanced.php?id=-1");
10025          $row[0] = "3. " . html_writer::tag('a', get_string('createuser', 'webservice'),
10026                          array('href' => $url));
10027          $row[1] = "";
10028          $row[2] = get_string('createuserdescription', 'webservice');
10029          $table->data[] = $row;
10030  
10031          /// 4. Add capability to users
10032          $row = array();
10033          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10034          $row[0] = "4. " . html_writer::tag('a', get_string('checkusercapability', 'webservice'),
10035                          array('href' => $url));
10036          $row[1] = "";
10037          $row[2] = get_string('checkusercapabilitydescription', 'webservice');
10038          $table->data[] = $row;
10039  
10040          /// 5. Select a web service
10041          $row = array();
10042          $url = new moodle_url("/admin/settings.php?section=externalservices");
10043          $row[0] = "5. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10044                          array('href' => $url));
10045          $row[1] = "";
10046          $row[2] = get_string('createservicedescription', 'webservice');
10047          $table->data[] = $row;
10048  
10049          /// 6. Add functions
10050          $row = array();
10051          $url = new moodle_url("/admin/settings.php?section=externalservices");
10052          $row[0] = "6. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10053                          array('href' => $url));
10054          $row[1] = "";
10055          $row[2] = get_string('addfunctionsdescription', 'webservice');
10056          $table->data[] = $row;
10057  
10058          /// 7. Add the specific user
10059          $row = array();
10060          $url = new moodle_url("/admin/settings.php?section=externalservices");
10061          $row[0] = "7. " . html_writer::tag('a', get_string('selectspecificuser', 'webservice'),
10062                          array('href' => $url));
10063          $row[1] = "";
10064          $row[2] = get_string('selectspecificuserdescription', 'webservice');
10065          $table->data[] = $row;
10066  
10067          /// 8. Create token for the specific user
10068          $row = array();
10069          $url = new moodle_url("/admin/webservice/tokens.php?sesskey=" . sesskey() . "&action=create");
10070          $row[0] = "8. " . html_writer::tag('a', get_string('createtokenforuser', 'webservice'),
10071                          array('href' => $url));
10072          $row[1] = "";
10073          $row[2] = get_string('createtokenforuserdescription', 'webservice');
10074          $table->data[] = $row;
10075  
10076          /// 9. Enable the documentation
10077          $row = array();
10078          $url = new moodle_url("/admin/search.php?query=enablewsdocumentation");
10079          $row[0] = "9. " . html_writer::tag('a', get_string('enabledocumentation', 'webservice'),
10080                          array('href' => $url));
10081          $status = '<span class="warning">' . get_string('no') . '</span>';
10082          if ($CFG->enablewsdocumentation) {
10083              $status = get_string('yes');
10084          }
10085          $row[1] = $status;
10086          $row[2] = get_string('enabledocumentationdescription', 'webservice');
10087          $table->data[] = $row;
10088  
10089          /// 10. Test the service
10090          $row = array();
10091          $url = new moodle_url("/admin/webservice/testclient.php");
10092          $row[0] = "10. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10093                          array('href' => $url));
10094          $row[1] = "";
10095          $row[2] = get_string('testwithtestclientdescription', 'webservice');
10096          $table->data[] = $row;
10097  
10098          $return .= html_writer::table($table);
10099  
10100          /// Users as clients with token
10101          $return .= $brtag . $brtag . $brtag;
10102          $return .= $OUTPUT->heading(get_string('userasclients', 'webservice'), 3, 'main');
10103          $table = new html_table();
10104          $table->head = array(get_string('step', 'webservice'), get_string('status'),
10105              get_string('description'));
10106          $table->colclasses = array('leftalign step', 'leftalign status', 'leftalign description');
10107          $table->id = 'userasclients';
10108          $table->attributes['class'] = 'admintable wsoverview generaltable';
10109          $table->data = array();
10110  
10111          $return .= $brtag . get_string('userasclientsdescription', 'webservice') .
10112                  $brtag . $brtag;
10113  
10114          /// 1. Enable Web Services
10115          $row = array();
10116          $url = new moodle_url("/admin/search.php?query=enablewebservices");
10117          $row[0] = "1. " . html_writer::tag('a', get_string('enablews', 'webservice'),
10118                          array('href' => $url));
10119          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
10120          if ($CFG->enablewebservices) {
10121              $status = get_string('yes');
10122          }
10123          $row[1] = $status;
10124          $row[2] = get_string('enablewsdescription', 'webservice');
10125          $table->data[] = $row;
10126  
10127          /// 2. Enable protocols
10128          $row = array();
10129          $url = new moodle_url("/admin/settings.php?section=webserviceprotocols");
10130          $row[0] = "2. " . html_writer::tag('a', get_string('enableprotocols', 'webservice'),
10131                          array('href' => $url));
10132          $status = html_writer::tag('span', get_string('none'), array('class' => 'badge badge-danger'));
10133          //retrieve activated protocol
10134          $active_protocols = empty($CFG->webserviceprotocols) ?
10135                  array() : explode(',', $CFG->webserviceprotocols);
10136          if (!empty($active_protocols)) {
10137              $status = "";
10138              foreach ($active_protocols as $protocol) {
10139                  $status .= $protocol . $brtag;
10140              }
10141          }
10142          $row[1] = $status;
10143          $row[2] = get_string('enableprotocolsdescription', 'webservice');
10144          $table->data[] = $row;
10145  
10146  
10147          /// 3. Select a web service
10148          $row = array();
10149          $url = new moodle_url("/admin/settings.php?section=externalservices");
10150          $row[0] = "3. " . html_writer::tag('a', get_string('selectservice', 'webservice'),
10151                          array('href' => $url));
10152          $row[1] = "";
10153          $row[2] = get_string('createserviceforusersdescription', 'webservice');
10154          $table->data[] = $row;
10155  
10156          /// 4. Add functions
10157          $row = array();
10158          $url = new moodle_url("/admin/settings.php?section=externalservices");
10159          $row[0] = "4. " . html_writer::tag('a', get_string('addfunctions', 'webservice'),
10160                          array('href' => $url));
10161          $row[1] = "";
10162          $row[2] = get_string('addfunctionsdescription', 'webservice');
10163          $table->data[] = $row;
10164  
10165          /// 5. Add capability to users
10166          $row = array();
10167          $url = new moodle_url("/admin/roles/check.php?contextid=1");
10168          $row[0] = "5. " . html_writer::tag('a', get_string('addcapabilitytousers', 'webservice'),
10169                          array('href' => $url));
10170          $row[1] = "";
10171          $row[2] = get_string('addcapabilitytousersdescription', 'webservice');
10172          $table->data[] = $row;
10173  
10174          /// 6. Test the service
10175          $row = array();
10176          $url = new moodle_url("/admin/webservice/testclient.php");
10177          $row[0] = "6. " . html_writer::tag('a', get_string('testwithtestclient', 'webservice'),
10178                          array('href' => $url));
10179          $row[1] = "";
10180          $row[2] = get_string('testauserwithtestclientdescription', 'webservice');
10181          $table->data[] = $row;
10182  
10183          $return .= html_writer::table($table);
10184  
10185          return highlight($query, $return);
10186      }
10187  
10188  }
10189  
10190  
10191  /**
10192   * Special class for web service protocol administration.
10193   *
10194   * @author Petr Skoda (skodak)
10195   */
10196  class admin_setting_managewebserviceprotocols extends admin_setting {
10197  
10198      /**
10199       * Calls parent::__construct with specific arguments
10200       */
10201      public function __construct() {
10202          $this->nosave = true;
10203          parent::__construct('webservicesui', get_string('manageprotocols', 'webservice'), '', '');
10204      }
10205  
10206      /**
10207       * Always returns true, does nothing
10208       *
10209       * @return true
10210       */
10211      public function get_setting() {
10212          return true;
10213      }
10214  
10215      /**
10216       * Always returns true, does nothing
10217       *
10218       * @return true
10219       */
10220      public function get_defaultsetting() {
10221          return true;
10222      }
10223  
10224      /**
10225       * Always returns '', does not write anything
10226       *
10227       * @return string Always returns ''
10228       */
10229      public function write_setting($data) {
10230      // do not write any setting
10231          return '';
10232      }
10233  
10234      /**
10235       * Checks if $query is one of the available webservices
10236       *
10237       * @param string $query The string to search for
10238       * @return bool Returns true if found, false if not
10239       */
10240      public function is_related($query) {
10241          if (parent::is_related($query)) {
10242              return true;
10243          }
10244  
10245          $protocols = core_component::get_plugin_list('webservice');
10246          foreach ($protocols as $protocol=>$location) {
10247              if (strpos($protocol, $query) !== false) {
10248                  return true;
10249              }
10250              $protocolstr = get_string('pluginname', 'webservice_'.$protocol);
10251              if (strpos(core_text::strtolower($protocolstr), $query) !== false) {
10252                  return true;
10253              }
10254          }
10255          return false;
10256      }
10257  
10258      /**
10259       * Builds the XHTML to display the control
10260       *
10261       * @param string $data Unused
10262       * @param string $query
10263       * @return string
10264       */
10265      public function output_html($data, $query='') {
10266          global $CFG, $OUTPUT;
10267  
10268          // display strings
10269          $stradministration = get_string('administration');
10270          $strsettings = get_string('settings');
10271          $stredit = get_string('edit');
10272          $strprotocol = get_string('protocol', 'webservice');
10273          $strenable = get_string('enable');
10274          $strdisable = get_string('disable');
10275          $strversion = get_string('version');
10276  
10277          $protocols_available = core_component::get_plugin_list('webservice');
10278          $active_protocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
10279          ksort($protocols_available);
10280  
10281          foreach ($active_protocols as $key=>$protocol) {
10282              if (empty($protocols_available[$protocol])) {
10283                  unset($active_protocols[$key]);
10284              }
10285          }
10286  
10287          $return = $OUTPUT->heading(get_string('actwebserviceshhdr', 'webservice'), 3, 'main');
10288          $return .= $OUTPUT->box_start('generalbox webservicesui');
10289  
10290          $table = new html_table();
10291          $table->head  = array($strprotocol, $strversion, $strenable, $strsettings);
10292          $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign');
10293          $table->id = 'webserviceprotocols';
10294          $table->attributes['class'] = 'admintable generaltable';
10295          $table->data  = array();
10296  
10297          // iterate through auth plugins and add to the display table
10298          $url = "$CFG->wwwroot/$CFG->admin/webservice/protocols.php?sesskey=" . sesskey();
10299          foreach ($protocols_available as $protocol => $location) {
10300              $name = get_string('pluginname', 'webservice_'.$protocol);
10301  
10302              $plugin = new stdClass();
10303              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/version.php')) {
10304                  include($CFG->dirroot.'/webservice/'.$protocol.'/version.php');
10305              }
10306              $version = isset($plugin->version) ? $plugin->version : '';
10307  
10308              // hide/show link
10309              if (in_array($protocol, $active_protocols)) {
10310                  $hideshow = "<a href=\"$url&amp;action=disable&amp;webservice=$protocol\">";
10311                  $hideshow .= $OUTPUT->pix_icon('t/hide', $strdisable) . '</a>';
10312                  $displayname = "<span>$name</span>";
10313              } else {
10314                  $hideshow = "<a href=\"$url&amp;action=enable&amp;webservice=$protocol\">";
10315                  $hideshow .= $OUTPUT->pix_icon('t/show', $strenable) . '</a>';
10316                  $displayname = "<span class=\"dimmed_text\">$name</span>";
10317              }
10318  
10319              // settings link
10320              if (file_exists($CFG->dirroot.'/webservice/'.$protocol.'/settings.php')) {
10321                  $settings = "<a href=\"settings.php?section=webservicesetting$protocol\">$strsettings</a>";
10322              } else {
10323                  $settings = '';
10324              }
10325  
10326              // add a row to the table
10327              $table->data[] = array($displayname, $version, $hideshow, $settings);
10328          }
10329          $return .= html_writer::table($table);
10330          $return .= get_string('configwebserviceplugins', 'webservice');
10331          $return .= $OUTPUT->box_end();
10332  
10333          return highlight($query, $return);
10334      }
10335  }
10336  
10337  
10338  /**
10339   * Special class for web service token administration.
10340   *
10341   * @author Jerome Mouneyrac
10342   */
10343  class admin_setting_managewebservicetokens extends admin_setting {
10344  
10345      /**
10346       * Calls parent::__construct with specific arguments
10347       */
10348      public function __construct() {
10349          $this->nosave = true;
10350          parent::__construct('webservicestokenui', get_string('managetokens', 'webservice'), '', '');
10351      }
10352  
10353      /**
10354       * Always returns true, does nothing
10355       *
10356       * @return true
10357       */
10358      public function get_setting() {
10359          return true;
10360      }
10361  
10362      /**
10363       * Always returns true, does nothing
10364       *
10365       * @return true
10366       */
10367      public function get_defaultsetting() {
10368          return true;
10369      }
10370  
10371      /**
10372       * Always returns '', does not write anything
10373       *
10374       * @return string Always returns ''
10375       */
10376      public function write_setting($data) {
10377      // do not write any setting
10378          return '';
10379      }
10380  
10381      /**
10382       * Builds the XHTML to display the control
10383       *
10384       * @param string $data Unused
10385       * @param string $query
10386       * @return string
10387       */
10388      public function output_html($data, $query='') {
10389          global $CFG, $OUTPUT;
10390  
10391          require_once($CFG->dirroot . '/webservice/classes/token_table.php');
10392          $baseurl = new moodle_url('/' . $CFG->admin . '/settings.php?section=webservicetokens');
10393  
10394          $return = $OUTPUT->box_start('generalbox webservicestokenui');
10395  
10396          if (has_capability('moodle/webservice:managealltokens', context_system::instance())) {
10397              $return .= \html_writer::div(get_string('onlyseecreatedtokens', 'webservice'));
10398          }
10399  
10400          $table = new \webservice\token_table('webservicetokens');
10401          $table->define_baseurl($baseurl);
10402          $table->attributes['class'] = 'admintable generaltable'; // Any need changing?
10403          $table->data  = array();
10404          ob_start();
10405          $table->out(10, false);
10406          $tablehtml = ob_get_contents();
10407          ob_end_clean();
10408          $return .= $tablehtml;
10409  
10410          $tokenpageurl = "$CFG->wwwroot/$CFG->admin/webservice/tokens.php?sesskey=" . sesskey();
10411  
10412          $return .= $OUTPUT->box_end();
10413          // add a token to the table
10414          $return .= "<a href=\"".$tokenpageurl."&amp;action=create\">";
10415          $return .= get_string('add')."</a>";
10416  
10417          return highlight($query, $return);
10418      }
10419  }
10420  
10421  
10422  /**
10423   * Colour picker
10424   *
10425   * @copyright 2010 Sam Hemelryk
10426   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10427   */
10428  class admin_setting_configcolourpicker extends admin_setting {
10429  
10430      /**
10431       * Information for previewing the colour
10432       *
10433       * @var array|null
10434       */
10435      protected $previewconfig = null;
10436  
10437      /**
10438       * Use default when empty.
10439       */
10440      protected $usedefaultwhenempty = true;
10441  
10442      /**
10443       *
10444       * @param string $name
10445       * @param string $visiblename
10446       * @param string $description
10447       * @param string $defaultsetting
10448       * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor');
10449       */
10450      public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig = null,
10451              $usedefaultwhenempty = true) {
10452          $this->previewconfig = $previewconfig;
10453          $this->usedefaultwhenempty = $usedefaultwhenempty;
10454          parent::__construct($name, $visiblename, $description, $defaultsetting);
10455          $this->set_force_ltr(true);
10456      }
10457  
10458      /**
10459       * Return the setting
10460       *
10461       * @return mixed returns config if successful else null
10462       */
10463      public function get_setting() {
10464          return $this->config_read($this->name);
10465      }
10466  
10467      /**
10468       * Saves the setting
10469       *
10470       * @param string $data
10471       * @return bool
10472       */
10473      public function write_setting($data) {
10474          $data = $this->validate($data);
10475          if ($data === false) {
10476              return  get_string('validateerror', 'admin');
10477          }
10478          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
10479      }
10480  
10481      /**
10482       * Validates the colour that was entered by the user
10483       *
10484       * @param string $data
10485       * @return string|false
10486       */
10487      protected function validate($data) {
10488          /**
10489           * List of valid HTML colour names
10490           *
10491           * @var array
10492           */
10493           $colornames = array(
10494              'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure',
10495              'beige', 'bisque', 'black', 'blanchedalmond', 'blue',
10496              'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse',
10497              'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson',
10498              'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray',
10499              'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta',
10500              'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
10501              'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray',
10502              'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
10503              'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick',
10504              'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro',
10505              'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green',
10506              'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo',
10507              'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen',
10508              'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',
10509              'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen',
10510              'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue',
10511              'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow',
10512              'lime', 'limegreen', 'linen', 'magenta', 'maroon',
10513              'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',
10514              'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
10515              'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',
10516              'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',
10517              'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',
10518              'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',
10519              'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',
10520              'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown',
10521              'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',
10522              'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan',
10523              'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white',
10524              'whitesmoke', 'yellow', 'yellowgreen'
10525          );
10526  
10527          if (preg_match('/^#?([[:xdigit:]]{3}){1,2}$/', $data)) {
10528              if (strpos($data, '#')!==0) {
10529                  $data = '#'.$data;
10530              }
10531              return $data;
10532          } else if (in_array(strtolower($data), $colornames)) {
10533              return $data;
10534          } else if (preg_match('/rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)/i', $data)) {
10535              return $data;
10536          } else if (preg_match('/rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)/i', $data)) {
10537              return $data;
10538          } else if (preg_match('/hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)/i', $data)) {
10539              return $data;
10540          } else if (preg_match('/hsla\(\d{0,3}\, ?\d{0,3}%,\d{0,3}%\, ?\d(\.\d)?\)/i', $data)) {
10541              return $data;
10542          } else if (($data == 'transparent') || ($data == 'currentColor') || ($data == 'inherit')) {
10543              return $data;
10544          } else if (empty($data)) {
10545              if ($this->usedefaultwhenempty){
10546                  return $this->defaultsetting;
10547              } else {
10548                  return '';
10549              }
10550          } else {
10551              return false;
10552          }
10553      }
10554  
10555      /**
10556       * Generates the HTML for the setting
10557       *
10558       * @global moodle_page $PAGE
10559       * @global core_renderer $OUTPUT
10560       * @param string $data
10561       * @param string $query
10562       */
10563      public function output_html($data, $query = '') {
10564          global $PAGE, $OUTPUT;
10565  
10566          $icon = new pix_icon('i/loading', get_string('loading', 'admin'), 'moodle', ['class' => 'loadingicon']);
10567          $context = (object) [
10568              'id' => $this->get_id(),
10569              'name' => $this->get_full_name(),
10570              'value' => $data,
10571              'icon' => $icon->export_for_template($OUTPUT),
10572              'haspreviewconfig' => !empty($this->previewconfig),
10573              'forceltr' => $this->get_force_ltr(),
10574              'readonly' => $this->is_readonly(),
10575          ];
10576  
10577          $element = $OUTPUT->render_from_template('core_admin/setting_configcolourpicker', $context);
10578          $PAGE->requires->js_init_call('M.util.init_colour_picker', array($this->get_id(), $this->previewconfig));
10579  
10580          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '',
10581              $this->get_defaultsetting(), $query);
10582      }
10583  
10584  }
10585  
10586  
10587  /**
10588   * Class used for uploading of one file into file storage,
10589   * the file name is stored in config table.
10590   *
10591   * Please note you need to implement your own '_pluginfile' callback function,
10592   * this setting only stores the file, it does not deal with file serving.
10593   *
10594   * @copyright 2013 Petr Skoda {@link http://skodak.org}
10595   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10596   */
10597  class admin_setting_configstoredfile extends admin_setting {
10598      /** @var array file area options - should be one file only */
10599      protected $options;
10600      /** @var string name of the file area */
10601      protected $filearea;
10602      /** @var int intemid */
10603      protected $itemid;
10604      /** @var string used for detection of changes */
10605      protected $oldhashes;
10606  
10607      /**
10608       * Create new stored file setting.
10609       *
10610       * @param string $name low level setting name
10611       * @param string $visiblename human readable setting name
10612       * @param string $description description of setting
10613       * @param mixed $filearea file area for file storage
10614       * @param int $itemid itemid for file storage
10615       * @param array $options file area options
10616       */
10617      public function __construct($name, $visiblename, $description, $filearea, $itemid = 0, array $options = null) {
10618          parent::__construct($name, $visiblename, $description, '');
10619          $this->filearea = $filearea;
10620          $this->itemid   = $itemid;
10621          $this->options  = (array)$options;
10622          $this->customcontrol = true;
10623      }
10624  
10625      /**
10626       * Applies defaults and returns all options.
10627       * @return array
10628       */
10629      protected function get_options() {
10630          global $CFG;
10631  
10632          require_once("$CFG->libdir/filelib.php");
10633          require_once("$CFG->dirroot/repository/lib.php");
10634          $defaults = array(
10635              'mainfile' => '', 'subdirs' => 0, 'maxbytes' => -1, 'maxfiles' => 1,
10636              'accepted_types' => '*', 'return_types' => FILE_INTERNAL, 'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED,
10637              'context' => context_system::instance());
10638          foreach($this->options as $k => $v) {
10639              $defaults[$k] = $v;
10640          }
10641  
10642          return $defaults;
10643      }
10644  
10645      public function get_setting() {
10646          return $this->config_read($this->name);
10647      }
10648  
10649      public function write_setting($data) {
10650          global $USER;
10651  
10652          // Let's not deal with validation here, this is for admins only.
10653          $current = $this->get_setting();
10654          if (empty($data) && $current === null) {
10655              // This will be the case when applying default settings (installation).
10656              return ($this->config_write($this->name, '') ? '' : get_string('errorsetting', 'admin'));
10657          } else if (!is_number($data)) {
10658              // Draft item id is expected here!
10659              return get_string('errorsetting', 'admin');
10660          }
10661  
10662          $options = $this->get_options();
10663          $fs = get_file_storage();
10664          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10665  
10666          $this->oldhashes = null;
10667          if ($current) {
10668              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10669              if ($file = $fs->get_file_by_hash($hash)) {
10670                  $this->oldhashes = $file->get_contenthash().$file->get_pathnamehash();
10671              }
10672              unset($file);
10673          }
10674  
10675          if ($fs->file_exists($options['context']->id, $component, $this->filearea, $this->itemid, '/', '.')) {
10676              // Make sure the settings form was not open for more than 4 days and draft areas deleted in the meantime.
10677              // But we can safely ignore that if the destination area is empty, so that the user is not prompt
10678              // with an error because the draft area does not exist, as he did not use it.
10679              $usercontext = context_user::instance($USER->id);
10680              if (!$fs->file_exists($usercontext->id, 'user', 'draft', $data, '/', '.') && $current !== '') {
10681                  return get_string('errorsetting', 'admin');
10682              }
10683          }
10684  
10685          file_save_draft_area_files($data, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10686          $files = $fs->get_area_files($options['context']->id, $component, $this->filearea, $this->itemid, 'sortorder,filepath,filename', false);
10687  
10688          $filepath = '';
10689          if ($files) {
10690              /** @var stored_file $file */
10691              $file = reset($files);
10692              $filepath = $file->get_filepath().$file->get_filename();
10693          }
10694  
10695          return ($this->config_write($this->name, $filepath) ? '' : get_string('errorsetting', 'admin'));
10696      }
10697  
10698      public function post_write_settings($original) {
10699          $options = $this->get_options();
10700          $fs = get_file_storage();
10701          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10702  
10703          $current = $this->get_setting();
10704          $newhashes = null;
10705          if ($current) {
10706              $hash = sha1('/'.$options['context']->id.'/'.$component.'/'.$this->filearea.'/'.$this->itemid.$current);
10707              if ($file = $fs->get_file_by_hash($hash)) {
10708                  $newhashes = $file->get_contenthash().$file->get_pathnamehash();
10709              }
10710              unset($file);
10711          }
10712  
10713          if ($this->oldhashes === $newhashes) {
10714              $this->oldhashes = null;
10715              return false;
10716          }
10717          $this->oldhashes = null;
10718  
10719          $callbackfunction = $this->updatedcallback;
10720          if (!empty($callbackfunction) and function_exists($callbackfunction)) {
10721              $callbackfunction($this->get_full_name());
10722          }
10723          return true;
10724      }
10725  
10726      public function output_html($data, $query = '') {
10727          global $PAGE, $CFG;
10728  
10729          $options = $this->get_options();
10730          $id = $this->get_id();
10731          $elname = $this->get_full_name();
10732          $draftitemid = file_get_submitted_draft_itemid($elname);
10733          $component = is_null($this->plugin) ? 'core' : $this->plugin;
10734          file_prepare_draft_area($draftitemid, $options['context']->id, $component, $this->filearea, $this->itemid, $options);
10735  
10736          // Filemanager form element implementation is far from optimal, we need to rework this if we ever fix it...
10737          require_once("$CFG->dirroot/lib/form/filemanager.php");
10738  
10739          $fmoptions = new stdClass();
10740          $fmoptions->mainfile       = $options['mainfile'];
10741          $fmoptions->maxbytes       = $options['maxbytes'];
10742          $fmoptions->maxfiles       = $options['maxfiles'];
10743          $fmoptions->client_id      = uniqid();
10744          $fmoptions->itemid         = $draftitemid;
10745          $fmoptions->subdirs        = $options['subdirs'];
10746          $fmoptions->target         = $id;
10747          $fmoptions->accepted_types = $options['accepted_types'];
10748          $fmoptions->return_types   = $options['return_types'];
10749          $fmoptions->context        = $options['context'];
10750          $fmoptions->areamaxbytes   = $options['areamaxbytes'];
10751  
10752          $fm = new form_filemanager($fmoptions);
10753          $output = $PAGE->get_renderer('core', 'files');
10754          $html = $output->render($fm);
10755  
10756          $html .= '<input value="'.$draftitemid.'" name="'.$elname.'" type="hidden" />';
10757          $html .= '<input value="" id="'.$id.'" type="hidden" />';
10758  
10759          return format_admin_setting($this, $this->visiblename,
10760              '<div class="form-filemanager" data-fieldtype="filemanager">'.$html.'</div>',
10761              $this->description, true, '', '', $query);
10762      }
10763  }
10764  
10765  
10766  /**
10767   * Administration interface for user specified regular expressions for device detection.
10768   *
10769   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10770   */
10771  class admin_setting_devicedetectregex extends admin_setting {
10772  
10773      /**
10774       * Calls parent::__construct with specific args
10775       *
10776       * @param string $name
10777       * @param string $visiblename
10778       * @param string $description
10779       * @param mixed $defaultsetting
10780       */
10781      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
10782          global $CFG;
10783          parent::__construct($name, $visiblename, $description, $defaultsetting);
10784      }
10785  
10786      /**
10787       * Return the current setting(s)
10788       *
10789       * @return array Current settings array
10790       */
10791      public function get_setting() {
10792          global $CFG;
10793  
10794          $config = $this->config_read($this->name);
10795          if (is_null($config)) {
10796              return null;
10797          }
10798  
10799          return $this->prepare_form_data($config);
10800      }
10801  
10802      /**
10803       * Save selected settings
10804       *
10805       * @param array $data Array of settings to save
10806       * @return bool
10807       */
10808      public function write_setting($data) {
10809          if (empty($data)) {
10810              $data = array();
10811          }
10812  
10813          if ($this->config_write($this->name, $this->process_form_data($data))) {
10814              return ''; // success
10815          } else {
10816              return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
10817          }
10818      }
10819  
10820      /**
10821       * Return XHTML field(s) for regexes
10822       *
10823       * @param array $data Array of options to set in HTML
10824       * @return string XHTML string for the fields and wrapping div(s)
10825       */
10826      public function output_html($data, $query='') {
10827          global $OUTPUT;
10828  
10829          $context = (object) [
10830              'expressions' => [],
10831              'name' => $this->get_full_name()
10832          ];
10833  
10834          if (empty($data)) {
10835              $looplimit = 1;
10836          } else {
10837              $looplimit = (count($data)/2)+1;
10838          }
10839  
10840          for ($i=0; $i<$looplimit; $i++) {
10841  
10842              $expressionname = 'expression'.$i;
10843  
10844              if (!empty($data[$expressionname])){
10845                  $expression = $data[$expressionname];
10846              } else {
10847                  $expression = '';
10848              }
10849  
10850              $valuename = 'value'.$i;
10851  
10852              if (!empty($data[$valuename])){
10853                  $value = $data[$valuename];
10854              } else {
10855                  $value= '';
10856              }
10857  
10858              $context->expressions[] = [
10859                  'index' => $i,
10860                  'expression' => $expression,
10861                  'value' => $value
10862              ];
10863          }
10864  
10865          $element = $OUTPUT->render_from_template('core_admin/setting_devicedetectregex', $context);
10866  
10867          return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', null, $query);
10868      }
10869  
10870      /**
10871       * Converts the string of regexes
10872       *
10873       * @see self::process_form_data()
10874       * @param $regexes string of regexes
10875       * @return array of form fields and their values
10876       */
10877      protected function prepare_form_data($regexes) {
10878  
10879          $regexes = json_decode($regexes);
10880  
10881          $form = array();
10882  
10883          $i = 0;
10884  
10885          foreach ($regexes as $value => $regex) {
10886              $expressionname  = 'expression'.$i;
10887              $valuename = 'value'.$i;
10888  
10889              $form[$expressionname] = $regex;
10890              $form[$valuename] = $value;
10891              $i++;
10892          }
10893  
10894          return $form;
10895      }
10896  
10897      /**
10898       * Converts the data from admin settings form into a string of regexes
10899       *
10900       * @see self::prepare_form_data()
10901       * @param array $data array of admin form fields and values
10902       * @return false|string of regexes
10903       */
10904      protected function process_form_data(array $form) {
10905  
10906          $count = count($form); // number of form field values
10907  
10908          if ($count % 2) {
10909              // we must get five fields per expression
10910              return false;
10911          }
10912  
10913          $regexes = array();
10914          for ($i = 0; $i < $count / 2; $i++) {
10915              $expressionname  = "expression".$i;
10916              $valuename       = "value".$i;
10917  
10918              $expression = trim($form['expression'.$i]);
10919              $value      = trim($form['value'.$i]);
10920  
10921              if (empty($expression)){
10922                  continue;
10923              }
10924  
10925              $regexes[$value] = $expression;
10926          }
10927  
10928          $regexes = json_encode($regexes);
10929  
10930          return $regexes;
10931      }
10932  
10933  }
10934  
10935  /**
10936   * Multiselect for current modules
10937   *
10938   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10939   */
10940  class admin_setting_configmultiselect_modules extends admin_setting_configmultiselect {
10941      private $excludesystem;
10942  
10943      /**
10944       * Calls parent::__construct - note array $choices is not required
10945       *
10946       * @param string $name setting name
10947       * @param string $visiblename localised setting name
10948       * @param string $description setting description
10949       * @param array $defaultsetting a plain array of default module ids
10950       * @param bool $excludesystem If true, excludes modules with 'system' archetype
10951       */
10952      public function __construct($name, $visiblename, $description, $defaultsetting = array(),
10953              $excludesystem = true) {
10954          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
10955          $this->excludesystem = $excludesystem;
10956      }
10957  
10958      /**
10959       * Loads an array of current module choices
10960       *
10961       * @return bool always return true
10962       */
10963      public function load_choices() {
10964          if (is_array($this->choices)) {
10965              return true;
10966          }
10967          $this->choices = array();
10968  
10969          global $CFG, $DB;
10970          $records = $DB->get_records('modules', array('visible'=>1), 'name');
10971          foreach ($records as $record) {
10972              // Exclude modules if the code doesn't exist
10973              if (file_exists("$CFG->dirroot/mod/$record->name/lib.php")) {
10974                  // Also exclude system modules (if specified)
10975                  if (!($this->excludesystem &&
10976                          plugin_supports('mod', $record->name, FEATURE_MOD_ARCHETYPE) ===
10977                          MOD_ARCHETYPE_SYSTEM)) {
10978                      $this->choices[$record->id] = $record->name;
10979                  }
10980              }
10981          }
10982          return true;
10983      }
10984  }
10985  
10986  /**
10987   * Admin setting to show if a php extension is enabled or not.
10988   *
10989   * @copyright 2013 Damyon Wiese
10990   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10991   */
10992  class admin_setting_php_extension_enabled extends admin_setting {
10993  
10994      /** @var string The name of the extension to check for */
10995      private $extension;
10996  
10997      /**
10998       * Calls parent::__construct with specific arguments
10999       */
11000      public function __construct($name, $visiblename, $description, $extension) {
11001          $this->extension = $extension;
11002          $this->nosave = true;
11003          parent::__construct($name, $visiblename, $description, '');
11004      }
11005  
11006      /**
11007       * Always returns true, does nothing
11008       *
11009       * @return true
11010       */
11011      public function get_setting() {
11012          return true;
11013      }
11014  
11015      /**
11016       * Always returns true, does nothing
11017       *
11018       * @return true
11019       */
11020      public function get_defaultsetting() {
11021          return true;
11022      }
11023  
11024      /**
11025       * Always returns '', does not write anything
11026       *
11027       * @return string Always returns ''
11028       */
11029      public function write_setting($data) {
11030          // Do not write any setting.
11031          return '';
11032      }
11033  
11034      /**
11035       * Outputs the html for this setting.
11036       * @return string Returns an XHTML string
11037       */
11038      public function output_html($data, $query='') {
11039          global $OUTPUT;
11040  
11041          $o = '';
11042          if (!extension_loaded($this->extension)) {
11043              $warning = $OUTPUT->pix_icon('i/warning', '', '', array('role' => 'presentation')) . ' ' . $this->description;
11044  
11045              $o .= format_admin_setting($this, $this->visiblename, $warning);
11046          }
11047          return $o;
11048      }
11049  }
11050  
11051  /**
11052   * Server timezone setting.
11053   *
11054   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11055   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11056   * @author    Petr Skoda <petr.skoda@totaralms.com>
11057   */
11058  class admin_setting_servertimezone extends admin_setting_configselect {
11059      /**
11060       * Constructor.
11061       */
11062      public function __construct() {
11063          $default = core_date::get_default_php_timezone();
11064          if ($default === 'UTC') {
11065              // Nobody really wants UTC, so instead default selection to the country that is confused by the UTC the most.
11066              $default = 'Europe/London';
11067          }
11068  
11069          parent::__construct('timezone',
11070              new lang_string('timezone', 'core_admin'),
11071              new lang_string('configtimezone', 'core_admin'), $default, null);
11072      }
11073  
11074      /**
11075       * Lazy load timezone options.
11076       * @return bool true if loaded, false if error
11077       */
11078      public function load_choices() {
11079          global $CFG;
11080          if (is_array($this->choices)) {
11081              return true;
11082          }
11083  
11084          $current = isset($CFG->timezone) ? $CFG->timezone : null;
11085          $this->choices = core_date::get_list_of_timezones($current, false);
11086          if ($current == 99) {
11087              // Do not show 99 unless it is current value, we want to get rid of it over time.
11088              $this->choices['99'] = new lang_string('timezonephpdefault', 'core_admin',
11089                  core_date::get_default_php_timezone());
11090          }
11091  
11092          return true;
11093      }
11094  }
11095  
11096  /**
11097   * Forced user timezone setting.
11098   *
11099   * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
11100   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11101   * @author    Petr Skoda <petr.skoda@totaralms.com>
11102   */
11103  class admin_setting_forcetimezone extends admin_setting_configselect {
11104      /**
11105       * Constructor.
11106       */
11107      public function __construct() {
11108          parent::__construct('forcetimezone',
11109              new lang_string('forcetimezone', 'core_admin'),
11110              new lang_string('helpforcetimezone', 'core_admin'), '99', null);
11111      }
11112  
11113      /**
11114       * Lazy load timezone options.
11115       * @return bool true if loaded, false if error
11116       */
11117      public function load_choices() {
11118          global $CFG;
11119          if (is_array($this->choices)) {
11120              return true;
11121          }
11122  
11123          $current = isset($CFG->forcetimezone) ? $CFG->forcetimezone : null;
11124          $this->choices = core_date::get_list_of_timezones($current, true);
11125          $this->choices['99'] = new lang_string('timezonenotforced', 'core_admin');
11126  
11127          return true;
11128      }
11129  }
11130  
11131  
11132  /**
11133   * Search setup steps info.
11134   *
11135   * @package core
11136   * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
11137   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11138   */
11139  class admin_setting_searchsetupinfo extends admin_setting {
11140  
11141      /**
11142       * Calls parent::__construct with specific arguments
11143       */
11144      public function __construct() {
11145          $this->nosave = true;
11146          parent::__construct('searchsetupinfo', '', '', '');
11147      }
11148  
11149      /**
11150       * Always returns true, does nothing
11151       *
11152       * @return true
11153       */
11154      public function get_setting() {
11155          return true;
11156      }
11157  
11158      /**
11159       * Always returns true, does nothing
11160       *
11161       * @return true
11162       */
11163      public function get_defaultsetting() {
11164          return true;
11165      }
11166  
11167      /**
11168       * Always returns '', does not write anything
11169       *
11170       * @param array $data
11171       * @return string Always returns ''
11172       */
11173      public function write_setting($data) {
11174          // Do not write any setting.
11175          return '';
11176      }
11177  
11178      /**
11179       * Builds the HTML to display the control
11180       *
11181       * @param string $data Unused
11182       * @param string $query
11183       * @return string
11184       */
11185      public function output_html($data, $query='') {
11186          global $CFG, $OUTPUT, $ADMIN;
11187  
11188          $return = '';
11189          $brtag = html_writer::empty_tag('br');
11190  
11191          $searchareas = \core_search\manager::get_search_areas_list();
11192          $anyenabled = !empty(\core_search\manager::get_search_areas_list(true));
11193          $anyindexed = false;
11194          foreach ($searchareas as $areaid => $searcharea) {
11195              list($componentname, $varname) = $searcharea->get_config_var_name();
11196              if (get_config($componentname, $varname . '_indexingstart')) {
11197                  $anyindexed = true;
11198                  break;
11199              }
11200          }
11201  
11202          $return .= $OUTPUT->heading(get_string('searchsetupinfo', 'admin'), 3, 'main');
11203  
11204          $table = new html_table();
11205          $table->head = array(get_string('step', 'search'), get_string('status'));
11206          $table->colclasses = array('leftalign step', 'leftalign status');
11207          $table->id = 'searchsetup';
11208          $table->attributes['class'] = 'admintable generaltable';
11209          $table->data = array();
11210  
11211          $return .= $brtag . get_string('searchsetupdescription', 'search') . $brtag . $brtag;
11212  
11213          // Select a search engine.
11214          $row = array();
11215          $url = new moodle_url('/admin/settings.php?section=manageglobalsearch#admin-searchengine');
11216          $row[0] = '1. ' . html_writer::tag('a', get_string('selectsearchengine', 'admin'),
11217                          array('href' => $url));
11218  
11219          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11220          if (!empty($CFG->searchengine)) {
11221              $status = html_writer::tag('span', get_string('pluginname', 'search_' . $CFG->searchengine),
11222                  array('class' => 'badge badge-success'));
11223  
11224          }
11225          $row[1] = $status;
11226          $table->data[] = $row;
11227  
11228          // Available areas.
11229          $row = array();
11230          $url = new moodle_url('/admin/searchareas.php');
11231          $row[0] = '2. ' . html_writer::tag('a', get_string('enablesearchareas', 'admin'),
11232                          array('href' => $url));
11233  
11234          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11235          if ($anyenabled) {
11236              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11237  
11238          }
11239          $row[1] = $status;
11240          $table->data[] = $row;
11241  
11242          // Setup search engine.
11243          $row = array();
11244          if (empty($CFG->searchengine)) {
11245              $row[0] = '3. ' . get_string('setupsearchengine', 'admin');
11246              $row[1] = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11247          } else {
11248              if ($ADMIN->locate('search' . $CFG->searchengine)) {
11249                  $url = new moodle_url('/admin/settings.php?section=search' . $CFG->searchengine);
11250                  $row[0] = '3. ' . html_writer::link($url, get_string('setupsearchengine', 'core_admin'));
11251              } else {
11252                  $row[0] = '3. ' . get_string('setupsearchengine', 'core_admin');
11253              }
11254  
11255              // Check the engine status.
11256              $searchengine = \core_search\manager::search_engine_instance();
11257              try {
11258                  $serverstatus = $searchengine->is_server_ready();
11259              } catch (\moodle_exception $e) {
11260                  $serverstatus = $e->getMessage();
11261              }
11262              if ($serverstatus === true) {
11263                  $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11264              } else {
11265                  $status = html_writer::tag('span', $serverstatus, array('class' => 'badge badge-danger'));
11266              }
11267              $row[1] = $status;
11268          }
11269          $table->data[] = $row;
11270  
11271          // Indexed data.
11272          $row = array();
11273          $url = new moodle_url('/admin/searchareas.php');
11274          $row[0] = '4. ' . html_writer::tag('a', get_string('indexdata', 'admin'), array('href' => $url));
11275          if ($anyindexed) {
11276              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11277          } else {
11278              $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11279          }
11280          $row[1] = $status;
11281          $table->data[] = $row;
11282  
11283          // Enable global search.
11284          $row = array();
11285          $url = new moodle_url("/admin/search.php?query=enableglobalsearch");
11286          $row[0] = '5. ' . html_writer::tag('a', get_string('enableglobalsearch', 'admin'),
11287                          array('href' => $url));
11288          $status = html_writer::tag('span', get_string('no'), array('class' => 'badge badge-danger'));
11289          if (\core_search\manager::is_global_search_enabled()) {
11290              $status = html_writer::tag('span', get_string('yes'), array('class' => 'badge badge-success'));
11291          }
11292          $row[1] = $status;
11293          $table->data[] = $row;
11294  
11295          $return .= html_writer::table($table);
11296  
11297          return highlight($query, $return);
11298      }
11299  
11300  }
11301  
11302  /**
11303   * Used to validate the contents of SCSS code and ensuring they are parsable.
11304   *
11305   * It does not attempt to detect undefined SCSS variables because it is designed
11306   * to be used without knowledge of other config/scss included.
11307   *
11308   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11309   * @copyright 2016 Dan Poltawski <dan@moodle.com>
11310   */
11311  class admin_setting_scsscode extends admin_setting_configtextarea {
11312  
11313      /**
11314       * Validate the contents of the SCSS to ensure its parsable. Does not
11315       * attempt to detect undefined scss variables.
11316       *
11317       * @param string $data The scss code from text field.
11318       * @return mixed bool true for success or string:error on failure.
11319       */
11320      public function validate($data) {
11321          if (empty($data)) {
11322              return true;
11323          }
11324  
11325          $scss = new core_scss();
11326          try {
11327              $scss->compile($data);
11328          } catch (ScssPhp\ScssPhp\Exception\ParserException $e) {
11329              return get_string('scssinvalid', 'admin', $e->getMessage());
11330          } catch (ScssPhp\ScssPhp\Exception\CompilerException $e) {
11331              // Silently ignore this - it could be a scss variable defined from somewhere
11332              // else which we are not examining here.
11333              return true;
11334          }
11335  
11336          return true;
11337      }
11338  }
11339  
11340  
11341  /**
11342   * Administration setting to define a list of file types.
11343   *
11344   * @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
11345   * @copyright 2017 David Mudrák <david@moodle.com>
11346   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11347   */
11348  class admin_setting_filetypes extends admin_setting_configtext {
11349  
11350      /** @var array Allow selection from these file types only. */
11351      protected $onlytypes = [];
11352  
11353      /** @var bool Allow selection of 'All file types' (will be stored as '*'). */
11354      protected $allowall = true;
11355  
11356      /** @var core_form\filetypes_util instance to use as a helper. */
11357      protected $util = null;
11358  
11359      /**
11360       * Constructor.
11361       *
11362       * @param string $name Unique ascii name like 'mycoresetting' or 'myplugin/mysetting'
11363       * @param string $visiblename Localised label of the setting
11364       * @param string $description Localised description of the setting
11365       * @param string $defaultsetting Default setting value.
11366       * @param array $options Setting widget options, an array with optional keys:
11367       *   'onlytypes' => array Allow selection from these file types only; for example ['onlytypes' => ['web_image']].
11368       *   'allowall' => bool Allow to select 'All file types', defaults to true. Does not apply if onlytypes are set.
11369       */
11370      public function __construct($name, $visiblename, $description, $defaultsetting = '', array $options = []) {
11371  
11372          parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW);
11373  
11374          if (array_key_exists('onlytypes', $options) && is_array($options['onlytypes'])) {
11375              $this->onlytypes = $options['onlytypes'];
11376          }
11377  
11378          if (!$this->onlytypes && array_key_exists('allowall', $options)) {
11379              $this->allowall = (bool)$options['allowall'];
11380          }
11381  
11382          $this->util = new \core_form\filetypes_util();
11383      }
11384  
11385      /**
11386       * Normalize the user's input and write it to the database as comma separated list.
11387       *
11388       * Comma separated list as a text representation of the array was chosen to
11389       * make this compatible with how the $CFG->courseoverviewfilesext values are stored.
11390       *
11391       * @param string $data Value submitted by the admin.
11392       * @return string Epty string if all good, error message otherwise.
11393       */
11394      public function write_setting($data) {
11395          return parent::write_setting(implode(',', $this->util->normalize_file_types($data)));
11396      }
11397  
11398      /**
11399       * Validate data before storage
11400       *
11401       * @param string $data The setting values provided by the admin
11402       * @return bool|string True if ok, the string if error found
11403       */
11404      public function validate($data) {
11405  
11406          // No need to call parent's validation here as we are PARAM_RAW.
11407  
11408          if ($this->util->is_listed($data, $this->onlytypes)) {
11409              return true;
11410  
11411          } else {
11412              $troublemakers = $this->util->get_not_listed($data, $this->onlytypes);
11413              return get_string('filetypesnotallowed', 'core_form', implode(' ', $troublemakers));
11414          }
11415      }
11416  
11417      /**
11418       * Return an HTML string for the setting element.
11419       *
11420       * @param string $data The current setting value
11421       * @param string $query Admin search query to be highlighted
11422       * @return string HTML to be displayed
11423       */
11424      public function output_html($data, $query='') {
11425          global $OUTPUT, $PAGE;
11426  
11427          $default = $this->get_defaultsetting();
11428          $context = (object) [
11429              'id' => $this->get_id(),
11430              'name' => $this->get_full_name(),
11431              'value' => $data,
11432              'descriptions' => $this->util->describe_file_types($data),
11433          ];
11434          $element = $OUTPUT->render_from_template('core_admin/setting_filetypes', $context);
11435  
11436          $PAGE->requires->js_call_amd('core_form/filetypes', 'init', [
11437              $this->get_id(),
11438              $this->visiblename->out(),
11439              $this->onlytypes,
11440              $this->allowall,
11441          ]);
11442  
11443          return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
11444      }
11445  
11446      /**
11447       * Should the values be always displayed in LTR mode?
11448       *
11449       * We always return true here because these values are not RTL compatible.
11450       *
11451       * @return bool True because these values are not RTL compatible.
11452       */
11453      public function get_force_ltr() {
11454          return true;
11455      }
11456  }
11457  
11458  /**
11459   * Used to validate the content and format of the age of digital consent map and ensuring it is parsable.
11460   *
11461   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11462   * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
11463   */
11464  class admin_setting_agedigitalconsentmap extends admin_setting_configtextarea {
11465  
11466      /**
11467       * Constructor.
11468       *
11469       * @param string $name
11470       * @param string $visiblename
11471       * @param string $description
11472       * @param mixed $defaultsetting string or array
11473       * @param mixed $paramtype
11474       * @param string $cols
11475       * @param string $rows
11476       */
11477      public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype = PARAM_RAW,
11478                                  $cols = '60', $rows = '8') {
11479          parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
11480          // Pre-set force LTR to false.
11481          $this->set_force_ltr(false);
11482      }
11483  
11484      /**
11485       * Validate the content and format of the age of digital consent map to ensure it is parsable.
11486       *
11487       * @param string $data The age of digital consent map from text field.
11488       * @return mixed bool true for success or string:error on failure.
11489       */
11490      public function validate($data) {
11491          if (empty($data)) {
11492              return true;
11493          }
11494  
11495          try {
11496              \core_auth\digital_consent::parse_age_digital_consent_map($data);
11497          } catch (\moodle_exception $e) {
11498              return get_string('invalidagedigitalconsent', 'admin', $e->getMessage());
11499          }
11500  
11501          return true;
11502      }
11503  }
11504  
11505  /**
11506   * Selection of plugins that can work as site policy handlers
11507   *
11508   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11509   * @copyright 2018 Marina Glancy
11510   */
11511  class admin_settings_sitepolicy_handler_select extends admin_setting_configselect {
11512  
11513      /**
11514       * Constructor
11515       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11516       *        for ones in config_plugins.
11517       * @param string $visiblename localised
11518       * @param string $description long localised info
11519       * @param string $defaultsetting
11520       */
11521      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11522          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11523      }
11524  
11525      /**
11526       * Lazy-load the available choices for the select box
11527       */
11528      public function load_choices() {
11529          if (during_initial_install()) {
11530              return false;
11531          }
11532          if (is_array($this->choices)) {
11533              return true;
11534          }
11535  
11536          $this->choices = ['' => new lang_string('sitepolicyhandlercore', 'core_admin')];
11537          $manager = new \core_privacy\local\sitepolicy\manager();
11538          $plugins = $manager->get_all_handlers();
11539          foreach ($plugins as $pname => $unused) {
11540              $this->choices[$pname] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11541                  ['name' => new lang_string('pluginname', $pname), 'component' => $pname]);
11542          }
11543  
11544          return true;
11545      }
11546  }
11547  
11548  /**
11549   * Used to validate theme presets code and ensuring they compile well.
11550   *
11551   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11552   * @copyright 2019 Bas Brands <bas@moodle.com>
11553   */
11554  class admin_setting_configthemepreset extends admin_setting_configselect {
11555  
11556      /** @var string The name of the theme to check for */
11557      private $themename;
11558  
11559      /**
11560       * Constructor
11561       * @param string $name unique ascii name, either 'mysetting' for settings that in config,
11562       * or 'myplugin/mysetting' for ones in config_plugins.
11563       * @param string $visiblename localised
11564       * @param string $description long localised info
11565       * @param string|int $defaultsetting
11566       * @param array $choices array of $value=>$label for each selection
11567       * @param string $themename name of theme to check presets for.
11568       */
11569      public function __construct($name, $visiblename, $description, $defaultsetting, $choices, $themename) {
11570          $this->themename = $themename;
11571          parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
11572      }
11573  
11574      /**
11575       * Write settings if validated
11576       *
11577       * @param string $data
11578       * @return string
11579       */
11580      public function write_setting($data) {
11581          $validated = $this->validate($data);
11582          if ($validated !== true) {
11583              return $validated;
11584          }
11585          return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
11586      }
11587  
11588      /**
11589       * Validate the preset file to ensure its parsable.
11590       *
11591       * @param string $data The preset file chosen.
11592       * @return mixed bool true for success or string:error on failure.
11593       */
11594      public function validate($data) {
11595  
11596          if (in_array($data, ['default.scss', 'plain.scss'])) {
11597              return true;
11598          }
11599  
11600          $fs = get_file_storage();
11601          $theme = theme_config::load($this->themename);
11602          $context = context_system::instance();
11603  
11604          // If the preset has not changed there is no need to validate it.
11605          if ($theme->settings->preset == $data) {
11606              return true;
11607          }
11608  
11609          if ($presetfile = $fs->get_file($context->id, 'theme_' . $this->themename, 'preset', 0, '/', $data)) {
11610              // This operation uses a lot of resources.
11611              raise_memory_limit(MEMORY_EXTRA);
11612              core_php_time_limit::raise(300);
11613  
11614              // TODO: MDL-62757 When changing anything in this method please do not forget to check
11615              // if the get_css_content_from_scss() method in class theme_config needs updating too.
11616  
11617              $compiler = new core_scss();
11618              $compiler->prepend_raw_scss($theme->get_pre_scss_code());
11619              $compiler->append_raw_scss($presetfile->get_content());
11620              if ($scssproperties = $theme->get_scss_property()) {
11621                  $compiler->setImportPaths($scssproperties[0]);
11622              }
11623              $compiler->append_raw_scss($theme->get_extra_scss_code());
11624  
11625              try {
11626                  $compiler->to_css();
11627              } catch (Exception $e) {
11628                  return get_string('invalidthemepreset', 'admin', $e->getMessage());
11629              }
11630  
11631              // Try to save memory.
11632              $compiler = null;
11633              unset($compiler);
11634          }
11635  
11636          return true;
11637      }
11638  }
11639  
11640  /**
11641   * Selection of plugins that can work as H5P libraries handlers
11642   *
11643   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11644   * @copyright 2020 Sara Arjona <sara@moodle.com>
11645   */
11646  class admin_settings_h5plib_handler_select extends admin_setting_configselect {
11647  
11648      /**
11649       * Constructor
11650       * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
11651       *        for ones in config_plugins.
11652       * @param string $visiblename localised
11653       * @param string $description long localised info
11654       * @param string $defaultsetting
11655       */
11656      public function __construct($name, $visiblename, $description, $defaultsetting = '') {
11657          parent::__construct($name, $visiblename, $description, $defaultsetting, null);
11658      }
11659  
11660      /**
11661       * Lazy-load the available choices for the select box
11662       */
11663      public function load_choices() {
11664          if (during_initial_install()) {
11665              return false;
11666          }
11667          if (is_array($this->choices)) {
11668              return true;
11669          }
11670  
11671          $this->choices = \core_h5p\local\library\autoloader::get_all_handlers();
11672          foreach ($this->choices as $name => $class) {
11673              $this->choices[$name] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
11674                  ['name' => new lang_string('pluginname', $name), 'component' => $name]);
11675          }
11676  
11677          return true;
11678      }
11679  }