2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Functions and classes used during installation, upgrades and for admin settings.
20 * ADMIN SETTINGS TREE INTRODUCTION
22 * This file performs the following tasks:
23 * -it defines the necessary objects and interfaces to build the Moodle
25 * -it defines the admin_externalpage_setup()
27 * ADMIN_SETTING OBJECTS
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.
33 * ADMIN_SETTINGPAGE OBJECTS
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.
39 * ADMIN_EXTERNALPAGE OBJECTS
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.
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
52 * $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
53 * $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
56 * Next, in foo.php, your file structure would resemble the following:
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();
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.
71 * ADMIN_CATEGORY OBJECTS
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.
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.
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.
89 * Please note that the $this->name field of any part_of_admin_tree must be
90 * UNIQUE throughout the ENTIRE admin tree.
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
96 * Original author: Vincenzo K. Marcovecchio
97 * Maintainer: Petr Skoda
101 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
102 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
105 defined('MOODLE_INTERNAL') || die();
108 require_once($CFG->libdir.'/ddllib.php');
109 require_once($CFG->libdir.'/xmlize.php');
110 require_once($CFG->libdir.'/messagelib.php');
112 define('INSECURE_DATAROOT_WARNING', 1);
113 define('INSECURE_DATAROOT_ERROR', 2);
116 * Automatically clean-up all plugin data and remove the plugin DB tables
118 * NOTE: do not call directly, use new /admin/plugins.php?uninstall=component instead!
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
125 function uninstall_plugin($type, $name) {
126 global $CFG, $DB, $OUTPUT;
128 // This may take a long time.
129 core_php_time_limit::raise();
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);
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.',
144 include("{$base}/db/subplugins.php");
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);
157 $component = $type . '_' . $name; // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
159 if ($type === 'mod') {
160 $pluginname = $name; // eg. 'forum'
161 if (get_string_manager()->string_exists('modulename', $component)) {
162 $strpluginname = get_string('modulename', $component);
164 $strpluginname = $component;
168 $pluginname = $component;
169 if (get_string_manager()->string_exists('pluginname', $component)) {
170 $strpluginname = get_string('pluginname', $component);
172 $strpluginname = $component;
176 echo $OUTPUT->heading($pluginname);
178 // Delete all tag areas, collections and instances associated with this plugin.
179 core_tag_area::uninstall($component);
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();
193 // Specific plugin type cleanup.
194 $plugininfo = core_plugin_manager::instance()->get_plugin_info($component);
196 $plugininfo->uninstall_cleanup();
197 core_plugin_manager::reset_caches();
201 // perform clean-up task common for all the plugin/subplugin types
203 //delete the web service functions and pre-built services
204 require_once($CFG->dirroot.'/lib/externallib.php');
205 external_delete_descriptions($component);
207 // delete calendar events
208 $DB->delete_records('event', array('modulename' => $pluginname));
209 $DB->delete_records('event', ['component' => $component]);
211 // Delete scheduled tasks.
212 $DB->delete_records('task_scheduled', array('component' => $component));
214 // Delete Inbound Message datakeys.
215 $DB->delete_records_select('messageinbound_datakeys',
216 'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($component));
218 // Delete Inbound Message handlers.
219 $DB->delete_records('messageinbound_handlers', array('component' => $component));
221 // delete all the logs
222 $DB->delete_records('log', array('module' => $pluginname));
224 // delete log_display information
225 $DB->delete_records('log_display', array('component' => $component));
227 // delete the module configuration records
228 unset_all_config_for_plugin($component);
229 if ($type === 'mod') {
230 unset_all_config_for_plugin($pluginname);
233 // delete message provider
234 message_provider_uninstall($component);
236 // delete the plugin tables
237 $xmldbfilepath = $plugindirectory . '/db/install.xml';
238 drop_plugin_tables($component, $xmldbfilepath, false);
239 if ($type === 'mod' or $type === 'block') {
240 // non-frankenstyle table prefixes
241 drop_plugin_tables($name, $xmldbfilepath, false);
244 // delete the capabilities that were defined by this module
245 capabilities_cleanup($component);
247 // Delete all remaining files in the filepool owned by the component.
248 $fs = get_file_storage();
249 $fs->delete_component_files($component);
251 // Finally purge all caches.
254 // Invalidate the hash used for upgrade detections.
255 set_config('allversionshash', '');
257 echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
261 * Returns the version of installed component
263 * @param string $component component name
264 * @param string $source either 'disk' or 'installed' - where to get the version information from
265 * @return string|bool version number or false if the component is not found
267 function get_component_version($component, $source='installed') {
270 list($type, $name) = core_component::normalize_component($component);
272 // moodle core or a core subsystem
273 if ($type === 'core') {
274 if ($source === 'installed') {
275 if (empty($CFG->version)) {
278 return $CFG->version;
281 if (!is_readable($CFG->dirroot.'/version.php')) {
284 $version = null; //initialize variable for IDEs
285 include($CFG->dirroot.'/version.php');
292 if ($type === 'mod') {
293 if ($source === 'installed') {
294 if ($CFG->version < 2013092001.02) {
295 return $DB->get_field('modules', 'version', array('name'=>$name));
297 return get_config('mod_'.$name, 'version');
301 $mods = core_component::get_plugin_list('mod');
302 if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
305 $plugin = new stdClass();
306 $plugin->version = null;
308 include($mods[$name].'/version.php');
309 return $plugin->version;
315 if ($type === 'block') {
316 if ($source === 'installed') {
317 if ($CFG->version < 2013092001.02) {
318 return $DB->get_field('block', 'version', array('name'=>$name));
320 return get_config('block_'.$name, 'version');
323 $blocks = core_component::get_plugin_list('block');
324 if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
327 $plugin = new stdclass();
328 include($blocks[$name].'/version.php');
329 return $plugin->version;
334 // all other plugin types
335 if ($source === 'installed') {
336 return get_config($type.'_'.$name, 'version');
338 $plugins = core_component::get_plugin_list($type);
339 if (empty($plugins[$name])) {
342 $plugin = new stdclass();
343 include($plugins[$name].'/version.php');
344 return $plugin->version;
350 * Delete all plugin tables
352 * @param string $name Name of plugin, used as table prefix
353 * @param string $file Path to install.xml file
354 * @param bool $feedback defaults to true
355 * @return bool Always returns true
357 function drop_plugin_tables($name, $file, $feedback=true) {
360 // first try normal delete
361 if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
365 // then try to find all tables that start with name and are not in any xml file
366 $used_tables = get_used_table_names();
368 $tables = $DB->get_tables();
370 /// Iterate over, fixing id fields as necessary
371 foreach ($tables as $table) {
372 if (in_array($table, $used_tables)) {
376 if (strpos($table, $name) !== 0) {
380 // found orphan table --> delete it
381 if ($DB->get_manager()->table_exists($table)) {
382 $xmldb_table = new xmldb_table($table);
383 $DB->get_manager()->drop_table($xmldb_table);
391 * Returns names of all known tables == tables that moodle knows about.
393 * @return array Array of lowercase table names
395 function get_used_table_names() {
396 $table_names = array();
397 $dbdirs = get_db_directories();
399 foreach ($dbdirs as $dbdir) {
400 $file = $dbdir.'/install.xml';
402 $xmldb_file = new xmldb_file($file);
404 if (!$xmldb_file->fileExists()) {
408 $loaded = $xmldb_file->loadXMLStructure();
409 $structure = $xmldb_file->getStructure();
411 if ($loaded and $tables = $structure->getTables()) {
412 foreach($tables as $table) {
413 $table_names[] = strtolower($table->getName());
422 * Returns list of all directories where we expect install.xml files
423 * @return array Array of paths
425 function get_db_directories() {
430 /// First, the main one (lib/db)
431 $dbdirs[] = $CFG->libdir.'/db';
433 /// Then, all the ones defined by core_component::get_plugin_types()
434 $plugintypes = core_component::get_plugin_types();
435 foreach ($plugintypes as $plugintype => $pluginbasedir) {
436 if ($plugins = core_component::get_plugin_list($plugintype)) {
437 foreach ($plugins as $plugin => $plugindir) {
438 $dbdirs[] = $plugindir.'/db';
447 * Try to obtain or release the cron lock.
448 * @param string $name name of lock
449 * @param int $until timestamp when this lock considered stale, null means remove lock unconditionally
450 * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
451 * @return bool true if lock obtained
453 function set_cron_lock($name, $until, $ignorecurrent=false) {
456 debugging("Tried to get a cron lock for a null fieldname");
460 // remove lock by force == remove from config table
461 if (is_null($until)) {
462 set_config($name, null);
466 if (!$ignorecurrent) {
467 // read value from db - other processes might have changed it
468 $value = $DB->get_field('config', 'value', array('name'=>$name));
470 if ($value and $value > time()) {
476 set_config($name, $until);
481 * Test if and critical warnings are present
484 function admin_critical_warnings_present() {
487 if (!has_capability('moodle/site:config', context_system::instance())) {
491 if (!isset($SESSION->admin_critical_warning)) {
492 $SESSION->admin_critical_warning = 0;
493 if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
494 $SESSION->admin_critical_warning = 1;
498 return $SESSION->admin_critical_warning;
502 * Detects if float supports at least 10 decimal digits
504 * Detects if float supports at least 10 decimal digits
505 * and also if float-->string conversion works as expected.
507 * @return bool true if problem found
509 function is_float_problem() {
510 $num1 = 2009010200.01;
511 $num2 = 2009010200.02;
513 return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
517 * Try to verify that dataroot is not accessible from web.
519 * Try to verify that dataroot is not accessible from web.
520 * It is not 100% correct but might help to reduce number of vulnerable sites.
521 * Protection from httpd.conf and .htaccess is not detected properly.
523 * @uses INSECURE_DATAROOT_WARNING
524 * @uses INSECURE_DATAROOT_ERROR
525 * @param bool $fetchtest try to test public access by fetching file, default false
526 * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
528 function is_dataroot_insecure($fetchtest=false) {
531 $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
533 $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
534 $rp = strrev(trim($rp, '/'));
535 $rp = explode('/', $rp);
537 if (strpos($siteroot, '/'.$r.'/') === 0) {
538 $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
540 break; // probably alias root
544 $siteroot = strrev($siteroot);
545 $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
547 if (strpos($dataroot, $siteroot) !== 0) {
552 return INSECURE_DATAROOT_WARNING;
555 // now try all methods to fetch a test file using http protocol
557 $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
558 preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
559 $httpdocroot = $matches[1];
560 $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
561 make_upload_directory('diag');
562 $testfile = $CFG->dataroot.'/diag/public.txt';
563 if (!file_exists($testfile)) {
564 file_put_contents($testfile, 'test file, do not delete');
565 @chmod($testfile, $CFG->filepermissions);
567 $teststr = trim(file_get_contents($testfile));
568 if (empty($teststr)) {
570 return INSECURE_DATAROOT_WARNING;
573 $testurl = $datarooturl.'/diag/public.txt';
574 if (extension_loaded('curl') and
575 !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
576 !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
577 ($ch = @curl_init($testurl)) !== false) {
578 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
579 curl_setopt($ch, CURLOPT_HEADER, false);
580 $data = curl_exec($ch);
581 if (!curl_errno($ch)) {
583 if ($data === $teststr) {
585 return INSECURE_DATAROOT_ERROR;
591 if ($data = @file_get_contents($testurl)) {
593 if ($data === $teststr) {
594 return INSECURE_DATAROOT_ERROR;
598 preg_match('|https?://([^/]+)|i', $testurl, $matches);
599 $sitename = $matches[1];
601 if ($fp = @fsockopen($sitename, 80, $error)) {
602 preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
603 $localurl = $matches[1];
604 $out = "GET $localurl HTTP/1.1\r\n";
605 $out .= "Host: $sitename\r\n";
606 $out .= "Connection: Close\r\n\r\n";
612 $data .= fgets($fp, 1024);
613 } else if (@fgets($fp, 1024) === "\r\n") {
619 if ($data === $teststr) {
620 return INSECURE_DATAROOT_ERROR;
624 return INSECURE_DATAROOT_WARNING;
628 * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
630 function enable_cli_maintenance_mode() {
633 if (file_exists("$CFG->dataroot/climaintenance.html")) {
634 unlink("$CFG->dataroot/climaintenance.html");
637 if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
638 $data = $CFG->maintenance_message;
639 $data = bootstrap_renderer::early_error_content($data, null, null, null);
640 $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
642 } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
643 $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
646 $data = get_string('sitemaintenance', 'admin');
647 $data = bootstrap_renderer::early_error_content($data, null, null, null);
648 $data = bootstrap_renderer::plain_page(get_string('sitemaintenancetitle', 'admin', $SITE->fullname), $data);
651 file_put_contents("$CFG->dataroot/climaintenance.html", $data);
652 chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
655 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
659 * Interface for anything appearing in the admin tree
661 * The interface that is implemented by anything that appears in the admin tree
662 * block. It forces inheriting classes to define a method for checking user permissions
663 * and methods for finding something in the admin tree.
665 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
667 interface part_of_admin_tree {
670 * Finds a named part_of_admin_tree.
672 * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
673 * and not parentable_part_of_admin_tree, then this function should only check if
674 * $this->name matches $name. If it does, it should return a reference to $this,
675 * otherwise, it should return a reference to NULL.
677 * If a class inherits parentable_part_of_admin_tree, this method should be called
678 * recursively on all child objects (assuming, of course, the parent object's name
679 * doesn't match the search criterion).
681 * @param string $name The internal name of the part_of_admin_tree we're searching for.
682 * @return mixed An object reference or a NULL reference.
684 public function locate($name);
687 * Removes named part_of_admin_tree.
689 * @param string $name The internal name of the part_of_admin_tree we want to remove.
690 * @return bool success.
692 public function prune($name);
696 * @param string $query
697 * @return mixed array-object structure of found settings and pages
699 public function search($query);
702 * Verifies current user's access to this part_of_admin_tree.
704 * Used to check if the current user has access to this part of the admin tree or
705 * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
706 * then this method is usually just a call to has_capability() in the site context.
708 * If a class inherits parentable_part_of_admin_tree, this method should return the
709 * logical OR of the return of check_access() on all child objects.
711 * @return bool True if the user has access, false if she doesn't.
713 public function check_access();
716 * Mostly useful for removing of some parts of the tree in admin tree block.
718 * @return True is hidden from normal list view
720 public function is_hidden();
723 * Show we display Save button at the page bottom?
726 public function show_save();
731 * Interface implemented by any part_of_admin_tree that has children.
733 * The interface implemented by any part_of_admin_tree that can be a parent
734 * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
735 * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
736 * include an add method for adding other part_of_admin_tree objects as children.
738 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
740 interface parentable_part_of_admin_tree extends part_of_admin_tree {
743 * Adds a part_of_admin_tree object to the admin tree.
745 * Used to add a part_of_admin_tree object to this object or a child of this
746 * object. $something should only be added if $destinationname matches
747 * $this->name. If it doesn't, add should be called on child objects that are
748 * also parentable_part_of_admin_tree's.
750 * $something should be appended as the last child in the $destinationname. If the
751 * $beforesibling is specified, $something should be prepended to it. If the given
752 * sibling is not found, $something should be appended to the end of $destinationname
753 * and a developer debugging message should be displayed.
755 * @param string $destinationname The internal name of the new parent for $something.
756 * @param part_of_admin_tree $something The object to be added.
757 * @return bool True on success, false on failure.
759 public function add($destinationname, $something, $beforesibling = null);
765 * The object used to represent folders (a.k.a. categories) in the admin tree block.
767 * Each admin_category object contains a number of part_of_admin_tree objects.
769 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
771 class admin_category implements parentable_part_of_admin_tree {
773 /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */
775 /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
777 /** @var string The displayed name for this category. Usually obtained through get_string() */
779 /** @var bool Should this category be hidden in admin tree block? */
781 /** @var mixed Either a string or an array or strings */
783 /** @var mixed Either a string or an array or strings */
786 /** @var array fast lookup category cache, all categories of one tree point to one cache */
787 protected $category_cache;
789 /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */
790 protected $sort = false;
791 /** @var bool If set to true children will be sorted in ascending order. */
792 protected $sortasc = true;
793 /** @var bool If set to true sub categories and pages will be split and then sorted.. */
794 protected $sortsplit = true;
795 /** @var bool $sorted True if the children have been sorted and don't need resorting */
796 protected $sorted = false;
799 * Constructor for an empty admin category
801 * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
802 * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
803 * @param bool $hidden hide category in admin tree block, defaults to false
805 public function __construct($name, $visiblename, $hidden=false) {
806 $this->children = array();
808 $this->visiblename = $visiblename;
809 $this->hidden = $hidden;
813 * Returns a reference to the part_of_admin_tree object with internal name $name.
815 * @param string $name The internal name of the object we want.
816 * @param bool $findpath initialize path and visiblepath arrays
817 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
820 public function locate($name, $findpath=false) {
821 if (!isset($this->category_cache[$this->name])) {
822 // somebody much have purged the cache
823 $this->category_cache[$this->name] = $this;
826 if ($this->name == $name) {
828 $this->visiblepath[] = $this->visiblename;
829 $this->path[] = $this->name;
834 // quick category lookup
835 if (!$findpath and isset($this->category_cache[$name])) {
836 return $this->category_cache[$name];
840 foreach($this->children as $childid=>$unused) {
841 if ($return = $this->children[$childid]->locate($name, $findpath)) {
846 if (!is_null($return) and $findpath) {
847 $return->visiblepath[] = $this->visiblename;
848 $return->path[] = $this->name;
857 * @param string query
858 * @return mixed array-object structure of found settings and pages
860 public function search($query) {
862 foreach ($this->get_children() as $child) {
863 $subsearch = $child->search($query);
864 if (!is_array($subsearch)) {
865 debugging('Incorrect search result from '.$child->name);
868 $result = array_merge($result, $subsearch);
874 * Removes part_of_admin_tree object with internal name $name.
876 * @param string $name The internal name of the object we want to remove.
877 * @return bool success
879 public function prune($name) {
881 if ($this->name == $name) {
882 return false; //can not remove itself
885 foreach($this->children as $precedence => $child) {
886 if ($child->name == $name) {
887 // clear cache and delete self
888 while($this->category_cache) {
889 // delete the cache, but keep the original array address
890 array_pop($this->category_cache);
892 unset($this->children[$precedence]);
894 } else if ($this->children[$precedence]->prune($name)) {
902 * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
904 * By default the new part of the tree is appended as the last child of the parent. You
905 * can specify a sibling node that the new part should be prepended to. If the given
906 * sibling is not found, the part is appended to the end (as it would be by default) and
907 * a developer debugging message is displayed.
909 * @throws coding_exception if the $beforesibling is empty string or is not string at all.
910 * @param string $destinationame The internal name of the immediate parent that we want for $something.
911 * @param mixed $something A part_of_admin_tree or setting instance to be added.
912 * @param string $beforesibling The name of the parent's child the $something should be prepended to.
913 * @return bool True if successfully added, false if $something can not be added.
915 public function add($parentname, $something, $beforesibling = null) {
918 $parent = $this->locate($parentname);
919 if (is_null($parent)) {
920 debugging('parent does not exist!');
924 if ($something instanceof part_of_admin_tree) {
925 if (!($parent instanceof parentable_part_of_admin_tree)) {
926 debugging('error - parts of tree can be inserted only into parentable parts');
929 if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
930 // The name of the node is already used, simply warn the developer that this should not happen.
931 // It is intentional to check for the debug level before performing the check.
932 debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
934 if (is_null($beforesibling)) {
935 // Append $something as the parent's last child.
936 $parent->children[] = $something;
938 if (!is_string($beforesibling) or trim($beforesibling) === '') {
939 throw new coding_exception('Unexpected value of the beforesibling parameter');
941 // Try to find the position of the sibling.
942 $siblingposition = null;
943 foreach ($parent->children as $childposition => $child) {
944 if ($child->name === $beforesibling) {
945 $siblingposition = $childposition;
949 if (is_null($siblingposition)) {
950 debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
951 $parent->children[] = $something;
953 $parent->children = array_merge(
954 array_slice($parent->children, 0, $siblingposition),
956 array_slice($parent->children, $siblingposition)
960 if ($something instanceof admin_category) {
961 if (isset($this->category_cache[$something->name])) {
962 debugging('Duplicate admin category name: '.$something->name);
964 $this->category_cache[$something->name] = $something;
965 $something->category_cache =& $this->category_cache;
966 foreach ($something->children as $child) {
967 // just in case somebody already added subcategories
968 if ($child instanceof admin_category) {
969 if (isset($this->category_cache[$child->name])) {
970 debugging('Duplicate admin category name: '.$child->name);
972 $this->category_cache[$child->name] = $child;
973 $child->category_cache =& $this->category_cache;
982 debugging('error - can not add this element');
989 * Checks if the user has access to anything in this category.
991 * @return bool True if the user has access to at least one child in this category, false otherwise.
993 public function check_access() {
994 foreach ($this->children as $child) {
995 if ($child->check_access()) {
1003 * Is this category hidden in admin tree block?
1005 * @return bool True if hidden
1007 public function is_hidden() {
1008 return $this->hidden;
1012 * Show we display Save button at the page bottom?
1015 public function show_save() {
1016 foreach ($this->children as $child) {
1017 if ($child->show_save()) {
1025 * Sets sorting on this category.
1027 * Please note this function doesn't actually do the sorting.
1028 * It can be called anytime.
1029 * Sorting occurs when the user calls get_children.
1030 * Code using the children array directly won't see the sorted results.
1032 * @param bool $sort If set to true children will be sorted, if false they won't be.
1033 * @param bool $asc If true sorting will be ascending, otherwise descending.
1034 * @param bool $split If true we sort pages and sub categories separately.
1036 public function set_sorting($sort, $asc = true, $split = true) {
1037 $this->sort = (bool)$sort;
1038 $this->sortasc = (bool)$asc;
1039 $this->sortsplit = (bool)$split;
1043 * Returns the children associated with this category.
1045 * @return part_of_admin_tree[]
1047 public function get_children() {
1048 // If we should sort and it hasn't already been sorted.
1049 if ($this->sort && !$this->sorted) {
1050 if ($this->sortsplit) {
1051 $categories = array();
1053 foreach ($this->children as $child) {
1054 if ($child instanceof admin_category) {
1055 $categories[] = $child;
1060 core_collator::asort_objects_by_property($categories, 'visiblename');
1061 core_collator::asort_objects_by_property($pages, 'visiblename');
1062 if (!$this->sortasc) {
1063 $categories = array_reverse($categories);
1064 $pages = array_reverse($pages);
1066 $this->children = array_merge($pages, $categories);
1068 core_collator::asort_objects_by_property($this->children, 'visiblename');
1069 if (!$this->sortasc) {
1070 $this->children = array_reverse($this->children);
1073 $this->sorted = true;
1075 return $this->children;
1079 * Magically gets a property from this object.
1082 * @return part_of_admin_tree[]
1083 * @throws coding_exception
1085 public function __get($property) {
1086 if ($property === 'children') {
1087 return $this->get_children();
1089 throw new coding_exception('Invalid property requested.');
1093 * Magically sets a property against this object.
1095 * @param string $property
1096 * @param mixed $value
1097 * @throws coding_exception
1099 public function __set($property, $value) {
1100 if ($property === 'children') {
1101 $this->sorted = false;
1102 $this->children = $value;
1104 throw new coding_exception('Invalid property requested.');
1109 * Checks if an inaccessible property is set.
1111 * @param string $property
1113 * @throws coding_exception
1115 public function __isset($property) {
1116 if ($property === 'children') {
1117 return isset($this->children);
1119 throw new coding_exception('Invalid property requested.');
1125 * Root of admin settings tree, does not have any parent.
1127 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1129 class admin_root extends admin_category {
1130 /** @var array List of errors */
1132 /** @var string search query */
1134 /** @var bool full tree flag - true means all settings required, false only pages required */
1136 /** @var bool flag indicating loaded tree */
1138 /** @var mixed site custom defaults overriding defaults in settings files*/
1139 public $custom_defaults;
1142 * @param bool $fulltree true means all settings required,
1143 * false only pages required
1145 public function __construct($fulltree) {
1148 parent::__construct('root', get_string('administration'), false);
1149 $this->errors = array();
1151 $this->fulltree = $fulltree;
1152 $this->loaded = false;
1154 $this->category_cache = array();
1156 // load custom defaults if found
1157 $this->custom_defaults = null;
1158 $defaultsfile = "$CFG->dirroot/local/defaults.php";
1159 if (is_readable($defaultsfile)) {
1160 $defaults = array();
1161 include($defaultsfile);
1162 if (is_array($defaults) and count($defaults)) {
1163 $this->custom_defaults = $defaults;
1169 * Empties children array, and sets loaded to false
1171 * @param bool $requirefulltree
1173 public function purge_children($requirefulltree) {
1174 $this->children = array();
1175 $this->fulltree = ($requirefulltree || $this->fulltree);
1176 $this->loaded = false;
1177 //break circular dependencies - this helps PHP 5.2
1178 while($this->category_cache) {
1179 array_pop($this->category_cache);
1181 $this->category_cache = array();
1187 * Links external PHP pages into the admin tree.
1189 * See detailed usage example at the top of this document (adminlib.php)
1191 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1193 class admin_externalpage implements part_of_admin_tree {
1195 /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1198 /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1199 public $visiblename;
1201 /** @var string The external URL that we should link to when someone requests this external page. */
1204 /** @var array The role capability/permission a user must have to access this external page. */
1205 public $req_capability;
1207 /** @var object The context in which capability/permission should be checked, default is site context. */
1210 /** @var bool hidden in admin tree block. */
1213 /** @var mixed either string or array of string */
1216 /** @var array list of visible names of page parents */
1217 public $visiblepath;
1220 * Constructor for adding an external page into the admin tree.
1222 * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1223 * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1224 * @param string $url The external URL that we should link to when someone requests this external page.
1225 * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1226 * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1227 * @param stdClass $context The context the page relates to. Not sure what happens
1228 * if you specify something other than system or front page. Defaults to system.
1230 public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1231 $this->name = $name;
1232 $this->visiblename = $visiblename;
1234 if (is_array($req_capability)) {
1235 $this->req_capability = $req_capability;
1237 $this->req_capability = array($req_capability);
1239 $this->hidden = $hidden;
1240 $this->context = $context;
1244 * Returns a reference to the part_of_admin_tree object with internal name $name.
1246 * @param string $name The internal name of the object we want.
1247 * @param bool $findpath defaults to false
1248 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1250 public function locate($name, $findpath=false) {
1251 if ($this->name == $name) {
1253 $this->visiblepath = array($this->visiblename);
1254 $this->path = array($this->name);
1264 * This function always returns false, required function by interface
1266 * @param string $name
1269 public function prune($name) {
1274 * Search using query
1276 * @param string $query
1277 * @return mixed array-object structure of found settings and pages
1279 public function search($query) {
1281 if (strpos(strtolower($this->name), $query) !== false) {
1283 } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1287 $result = new stdClass();
1288 $result->page = $this;
1289 $result->settings = array();
1290 return array($this->name => $result);
1297 * Determines if the current user has access to this external page based on $this->req_capability.
1299 * @return bool True if user has access, false otherwise.
1301 public function check_access() {
1303 $context = empty($this->context) ? context_system::instance() : $this->context;
1304 foreach($this->req_capability as $cap) {
1305 if (has_capability($cap, $context)) {
1313 * Is this external page hidden in admin tree block?
1315 * @return bool True if hidden
1317 public function is_hidden() {
1318 return $this->hidden;
1322 * Show we display Save button at the page bottom?
1325 public function show_save() {
1331 * Used to store details of the dependency between two settings elements.
1333 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1334 * @copyright 2017 Davo Smith, Synergy Learning
1336 class admin_settingdependency {
1337 /** @var string the name of the setting to be shown/hidden */
1338 public $settingname;
1339 /** @var string the setting this is dependent on */
1340 public $dependenton;
1341 /** @var string the condition to show/hide the element */
1343 /** @var string the value to compare against */
1346 /** @var string[] list of valid conditions */
1347 private static $validconditions = ['checked', 'notchecked', 'noitemselected', 'eq', 'neq', 'in'];
1350 * admin_settingdependency constructor.
1351 * @param string $settingname
1352 * @param string $dependenton
1353 * @param string $condition
1354 * @param string $value
1355 * @throws \coding_exception
1357 public function __construct($settingname, $dependenton, $condition, $value) {
1358 $this->settingname = $this->parse_name($settingname);
1359 $this->dependenton = $this->parse_name($dependenton);
1360 $this->condition = $condition;
1361 $this->value = $value;
1363 if (!in_array($this->condition, self::$validconditions)) {
1364 throw new coding_exception("Invalid condition '$condition'");
1369 * Convert the setting name into the form field name.
1370 * @param string $name
1373 private function parse_name($name) {
1374 $bits = explode('/', $name);
1375 $name = array_pop($bits);
1378 $plugin = array_pop($bits);
1379 if ($plugin === 'moodle') {
1383 return 's_'.$plugin.'_'.$name;
1387 * Gather together all the dependencies in a format suitable for initialising javascript
1388 * @param admin_settingdependency[] $dependencies
1391 public static function prepare_for_javascript($dependencies) {
1393 foreach ($dependencies as $d) {
1394 if (!isset($result[$d->dependenton])) {
1395 $result[$d->dependenton] = [];
1397 if (!isset($result[$d->dependenton][$d->condition])) {
1398 $result[$d->dependenton][$d->condition] = [];
1400 if (!isset($result[$d->dependenton][$d->condition][$d->value])) {
1401 $result[$d->dependenton][$d->condition][$d->value] = [];
1403 $result[$d->dependenton][$d->condition][$d->value][] = $d->settingname;
1410 * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1412 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1414 class admin_settingpage implements part_of_admin_tree {
1416 /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1419 /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1420 public $visiblename;
1422 /** @var mixed An array of admin_setting objects that are part of this setting page. */
1425 /** @var admin_settingdependency[] list of settings to hide when certain conditions are met */
1426 protected $dependencies = [];
1428 /** @var array The role capability/permission a user must have to access this external page. */
1429 public $req_capability;
1431 /** @var object The context in which capability/permission should be checked, default is site context. */
1434 /** @var bool hidden in admin tree block. */
1437 /** @var mixed string of paths or array of strings of paths */
1440 /** @var array list of visible names of page parents */
1441 public $visiblepath;
1444 * see admin_settingpage for details of this function
1446 * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1447 * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1448 * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1449 * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1450 * @param stdClass $context The context the page relates to. Not sure what happens
1451 * if you specify something other than system or front page. Defaults to system.
1453 public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1454 $this->settings = new stdClass();
1455 $this->name = $name;
1456 $this->visiblename = $visiblename;
1457 if (is_array($req_capability)) {
1458 $this->req_capability = $req_capability;
1460 $this->req_capability = array($req_capability);
1462 $this->hidden = $hidden;
1463 $this->context = $context;
1467 * see admin_category
1469 * @param string $name
1470 * @param bool $findpath
1471 * @return mixed Object (this) if name == this->name, else returns null
1473 public function locate($name, $findpath=false) {
1474 if ($this->name == $name) {
1476 $this->visiblepath = array($this->visiblename);
1477 $this->path = array($this->name);
1487 * Search string in settings page.
1489 * @param string $query
1492 public function search($query) {
1495 foreach ($this->settings as $setting) {
1496 if ($setting->is_related($query)) {
1497 $found[] = $setting;
1502 $result = new stdClass();
1503 $result->page = $this;
1504 $result->settings = $found;
1505 return array($this->name => $result);
1509 if (strpos(strtolower($this->name), $query) !== false) {
1511 } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1515 $result = new stdClass();
1516 $result->page = $this;
1517 $result->settings = array();
1518 return array($this->name => $result);
1525 * This function always returns false, required by interface
1527 * @param string $name
1528 * @return bool Always false
1530 public function prune($name) {
1535 * adds an admin_setting to this admin_settingpage
1537 * 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
1538 * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1540 * @param object $setting is the admin_setting object you want to add
1541 * @return bool true if successful, false if not
1543 public function add($setting) {
1544 if (!($setting instanceof admin_setting)) {
1545 debugging('error - not a setting instance');
1549 $name = $setting->name;
1550 if ($setting->plugin) {
1551 $name = $setting->plugin . $name;
1553 $this->settings->{$name} = $setting;
1558 * Hide the named setting if the specified condition is matched.
1560 * @param string $settingname
1561 * @param string $dependenton
1562 * @param string $condition
1563 * @param string $value
1565 public function hide_if($settingname, $dependenton, $condition = 'notchecked', $value = '1') {
1566 $this->dependencies[] = new admin_settingdependency($settingname, $dependenton, $condition, $value);
1568 // Reformat the dependency name to the plugin | name format used in the display.
1569 $dependenton = str_replace('/', ' | ', $dependenton);
1571 // Let the setting know, so it can be displayed underneath.
1572 $findname = str_replace('/', '', $settingname);
1573 foreach ($this->settings as $name => $setting) {
1574 if ($name === $findname) {
1575 $setting->add_dependent_on($dependenton);
1581 * see admin_externalpage
1583 * @return bool Returns true for yes false for no
1585 public function check_access() {
1587 $context = empty($this->context) ? context_system::instance() : $this->context;
1588 foreach($this->req_capability as $cap) {
1589 if (has_capability($cap, $context)) {
1597 * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1598 * @return string Returns an XHTML string
1600 public function output_html() {
1601 $adminroot = admin_get_root();
1602 $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1603 foreach($this->settings as $setting) {
1604 $fullname = $setting->get_full_name();
1605 if (array_key_exists($fullname, $adminroot->errors)) {
1606 $data = $adminroot->errors[$fullname]->data;
1608 $data = $setting->get_setting();
1609 // do not use defaults if settings not available - upgrade settings handles the defaults!
1611 $return .= $setting->output_html($data);
1613 $return .= '</fieldset>';
1618 * Is this settings page hidden in admin tree block?
1620 * @return bool True if hidden
1622 public function is_hidden() {
1623 return $this->hidden;
1627 * Show we display Save button at the page bottom?
1630 public function show_save() {
1631 foreach($this->settings as $setting) {
1632 if (empty($setting->nosave)) {
1640 * Should any of the settings on this page be shown / hidden based on conditions?
1643 public function has_dependencies() {
1644 return (bool)$this->dependencies;
1648 * Format the setting show/hide conditions ready to initialise the page javascript
1651 public function get_dependencies_for_javascript() {
1652 if (!$this->has_dependencies()) {
1655 return admin_settingdependency::prepare_for_javascript($this->dependencies);
1661 * Admin settings class. Only exists on setting pages.
1662 * Read & write happens at this level; no authentication.
1664 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1666 abstract class admin_setting {
1667 /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1669 /** @var string localised name */
1670 public $visiblename;
1671 /** @var string localised long description in Markdown format */
1672 public $description;
1673 /** @var mixed Can be string or array of string */
1674 public $defaultsetting;
1676 public $updatedcallback;
1677 /** @var mixed can be String or Null. Null means main config table */
1678 public $plugin; // null means main config table
1679 /** @var bool true indicates this setting does not actually save anything, just information */
1680 public $nosave = false;
1681 /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1682 public $affectsmodinfo = false;
1683 /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */
1684 private $flags = array();
1685 /** @var bool Whether this field must be forced LTR. */
1686 private $forceltr = null;
1687 /** @var array list of other settings that may cause this setting to be hidden */
1688 private $dependenton = [];
1689 /** @var bool Whether this setting uses a custom form control */
1690 protected $customcontrol = false;
1694 * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1695 * or 'myplugin/mysetting' for ones in config_plugins.
1696 * @param string $visiblename localised name
1697 * @param string $description localised long description
1698 * @param mixed $defaultsetting string or array depending on implementation
1700 public function __construct($name, $visiblename, $description, $defaultsetting) {
1701 $this->parse_setting_name($name);
1702 $this->visiblename = $visiblename;
1703 $this->description = $description;
1704 $this->defaultsetting = $defaultsetting;
1708 * Generic function to add a flag to this admin setting.
1710 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1711 * @param bool $default - The default for the flag
1712 * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name.
1713 * @param string $displayname - The display name for this flag. Used as a label next to the checkbox.
1715 protected function set_flag_options($enabled, $default, $shortname, $displayname) {
1716 if (empty($this->flags[$shortname])) {
1717 $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname);
1719 $this->flags[$shortname]->set_options($enabled, $default);
1724 * Set the enabled options flag on this admin setting.
1726 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1727 * @param bool $default - The default for the flag
1729 public function set_enabled_flag_options($enabled, $default) {
1730 $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin'));
1734 * Set the advanced options flag on this admin setting.
1736 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1737 * @param bool $default - The default for the flag
1739 public function set_advanced_flag_options($enabled, $default) {
1740 $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced'));
1745 * Set the locked options flag on this admin setting.
1747 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1748 * @param bool $default - The default for the flag
1750 public function set_locked_flag_options($enabled, $default) {
1751 $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
1755 * Set the required options flag on this admin setting.
1757 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED.
1758 * @param bool $default - The default for the flag.
1760 public function set_required_flag_options($enabled, $default) {
1761 $this->set_flag_options($enabled, $default, 'required', new lang_string('required', 'core_admin'));
1765 * Get the currently saved value for a setting flag
1767 * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting.
1770 public function get_setting_flag_value(admin_setting_flag $flag) {
1771 $value = $this->config_read($this->name . '_' . $flag->get_shortname());
1772 if (!isset($value)) {
1773 $value = $flag->get_default();
1776 return !empty($value);
1780 * Get the list of defaults for the flags on this setting.
1782 * @param array of strings describing the defaults for this setting. This is appended to by this function.
1784 public function get_setting_flag_defaults(& $defaults) {
1785 foreach ($this->flags as $flag) {
1786 if ($flag->is_enabled() && $flag->get_default()) {
1787 $defaults[] = $flag->get_displayname();
1793 * Output the input fields for the advanced and locked flags on this setting.
1795 * @param bool $adv - The current value of the advanced flag.
1796 * @param bool $locked - The current value of the locked flag.
1797 * @return string $output - The html for the flags.
1799 public function output_setting_flags() {
1802 foreach ($this->flags as $flag) {
1803 if ($flag->is_enabled()) {
1804 $output .= $flag->output_setting_flag($this);
1808 if (!empty($output)) {
1809 return html_writer::tag('span', $output, array('class' => 'adminsettingsflags'));
1815 * Write the values of the flags for this admin setting.
1817 * @param array $data - The data submitted from the form or null to set the default value for new installs.
1818 * @return bool - true if successful.
1820 public function write_setting_flags($data) {
1822 foreach ($this->flags as $flag) {
1823 $result = $result && $flag->write_setting_flag($this, $data);
1829 * Set up $this->name and potentially $this->plugin
1831 * Set up $this->name and possibly $this->plugin based on whether $name looks
1832 * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1833 * on the names, that is, output a developer debug warning if the name
1834 * contains anything other than [a-zA-Z0-9_]+.
1836 * @param string $name the setting name passed in to the constructor.
1838 private function parse_setting_name($name) {
1839 $bits = explode('/', $name);
1840 if (count($bits) > 2) {
1841 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1843 $this->name = array_pop($bits);
1844 if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1845 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1847 if (!empty($bits)) {
1848 $this->plugin = array_pop($bits);
1849 if ($this->plugin === 'moodle') {
1850 $this->plugin = null;
1851 } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1852 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1858 * Returns the fullname prefixed by the plugin
1861 public function get_full_name() {
1862 return 's_'.$this->plugin.'_'.$this->name;
1866 * Returns the ID string based on plugin and name
1869 public function get_id() {
1870 return 'id_s_'.$this->plugin.'_'.$this->name;
1874 * @param bool $affectsmodinfo If true, changes to this setting will
1875 * cause the course cache to be rebuilt
1877 public function set_affects_modinfo($affectsmodinfo) {
1878 $this->affectsmodinfo = $affectsmodinfo;
1882 * Returns the config if possible
1884 * @return mixed returns config if successful else null
1886 public function config_read($name) {
1888 if (!empty($this->plugin)) {
1889 $value = get_config($this->plugin, $name);
1890 return $value === false ? NULL : $value;
1893 if (isset($CFG->$name)) {
1902 * Used to set a config pair and log change
1904 * @param string $name
1905 * @param mixed $value Gets converted to string if not null
1906 * @return bool Write setting to config table
1908 public function config_write($name, $value) {
1909 global $DB, $USER, $CFG;
1911 if ($this->nosave) {
1915 // make sure it is a real change
1916 $oldvalue = get_config($this->plugin, $name);
1917 $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1918 $value = is_null($value) ? null : (string)$value;
1920 if ($oldvalue === $value) {
1925 set_config($name, $value, $this->plugin);
1927 // Some admin settings affect course modinfo
1928 if ($this->affectsmodinfo) {
1929 // Clear course cache for all courses
1930 rebuild_course_cache(0, true);
1933 $this->add_to_config_log($name, $oldvalue, $value);
1935 return true; // BC only
1939 * Log config changes if necessary.
1940 * @param string $name
1941 * @param string $oldvalue
1942 * @param string $value
1944 protected function add_to_config_log($name, $oldvalue, $value) {
1945 add_to_config_log($name, $oldvalue, $value, $this->plugin);
1949 * Returns current value of this setting
1950 * @return mixed array or string depending on instance, NULL means not set yet
1952 public abstract function get_setting();
1955 * Returns default setting if exists
1956 * @return mixed array or string depending on instance; NULL means no default, user must supply
1958 public function get_defaultsetting() {
1959 $adminroot = admin_get_root(false, false);
1960 if (!empty($adminroot->custom_defaults)) {
1961 $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1962 if (isset($adminroot->custom_defaults[$plugin])) {
1963 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
1964 return $adminroot->custom_defaults[$plugin][$this->name];
1968 return $this->defaultsetting;
1974 * @param mixed $data string or array, must not be NULL
1975 * @return string empty string if ok, string error message otherwise
1977 public abstract function write_setting($data);
1980 * Return part of form with setting
1981 * This function should always be overwritten
1983 * @param mixed $data array or string depending on setting
1984 * @param string $query
1987 public function output_html($data, $query='') {
1988 // should be overridden
1993 * Function called if setting updated - cleanup, cache reset, etc.
1994 * @param string $functionname Sets the function name
1997 public function set_updatedcallback($functionname) {
1998 $this->updatedcallback = $functionname;
2002 * Execute postupdatecallback if necessary.
2003 * @param mixed $original original value before write_setting()
2004 * @return bool true if changed, false if not.
2006 public function post_write_settings($original) {
2007 // Comparison must work for arrays too.
2008 if (serialize($original) === serialize($this->get_setting())) {
2012 $callbackfunction = $this->updatedcallback;
2013 if (!empty($callbackfunction) and is_callable($callbackfunction)) {
2014 $callbackfunction($this->get_full_name());
2020 * Is setting related to query text - used when searching
2021 * @param string $query
2024 public function is_related($query) {
2025 if (strpos(strtolower($this->name), $query) !== false) {
2028 if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
2031 if (strpos(core_text::strtolower($this->description), $query) !== false) {
2034 $current = $this->get_setting();
2035 if (!is_null($current)) {
2036 if (is_string($current)) {
2037 if (strpos(core_text::strtolower($current), $query) !== false) {
2042 $default = $this->get_defaultsetting();
2043 if (!is_null($default)) {
2044 if (is_string($default)) {
2045 if (strpos(core_text::strtolower($default), $query) !== false) {
2054 * Get whether this should be displayed in LTR mode.
2058 public function get_force_ltr() {
2059 return $this->forceltr;
2063 * Set whether to force LTR or not.
2065 * @param bool $value True when forced, false when not force, null when unknown.
2067 public function set_force_ltr($value) {
2068 $this->forceltr = $value;
2072 * Add a setting to the list of those that could cause this one to be hidden
2073 * @param string $dependenton
2075 public function add_dependent_on($dependenton) {
2076 $this->dependenton[] = $dependenton;
2080 * Get a list of the settings that could cause this one to be hidden.
2083 public function get_dependent_on() {
2084 return $this->dependenton;
2088 * Whether this setting uses a custom form control.
2089 * This function is especially useful to decide if we should render a label element for this setting or not.
2093 public function has_custom_form_control(): bool {
2094 return $this->customcontrol;
2099 * An additional option that can be applied to an admin setting.
2100 * The currently supported options are 'ADVANCED', 'LOCKED' and 'REQUIRED'.
2102 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2104 class admin_setting_flag {
2105 /** @var bool Flag to indicate if this option can be toggled for this setting */
2106 private $enabled = false;
2107 /** @var bool Flag to indicate if this option defaults to true or false */
2108 private $default = false;
2109 /** @var string Short string used to create setting name - e.g. 'adv' */
2110 private $shortname = '';
2111 /** @var string String used as the label for this flag */
2112 private $displayname = '';
2113 /** @const Checkbox for this flag is displayed in admin page */
2114 const ENABLED = true;
2115 /** @const Checkbox for this flag is not displayed in admin page */
2116 const DISABLED = false;
2121 * @param bool $enabled Can this option can be toggled.
2122 * Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2123 * @param bool $default The default checked state for this setting option.
2124 * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
2125 * @param string $displayname The displayname of this flag. Used as a label for the flag.
2127 public function __construct($enabled, $default, $shortname, $displayname) {
2128 $this->shortname = $shortname;
2129 $this->displayname = $displayname;
2130 $this->set_options($enabled, $default);
2134 * Update the values of this setting options class
2136 * @param bool $enabled Can this option can be toggled.
2137 * Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
2138 * @param bool $default The default checked state for this setting option.
2140 public function set_options($enabled, $default) {
2141 $this->enabled = $enabled;
2142 $this->default = $default;
2146 * Should this option appear in the interface and be toggleable?
2148 * @return bool Is it enabled?
2150 public function is_enabled() {
2151 return $this->enabled;
2155 * Should this option be checked by default?
2157 * @return bool Is it on by default?
2159 public function get_default() {
2160 return $this->default;
2164 * Return the short name for this flag. e.g. 'adv' or 'locked'
2168 public function get_shortname() {
2169 return $this->shortname;
2173 * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
2177 public function get_displayname() {
2178 return $this->displayname;
2182 * Save the submitted data for this flag - or set it to the default if $data is null.
2184 * @param admin_setting $setting - The admin setting for this flag
2185 * @param array $data - The data submitted from the form or null to set the default value for new installs.
2188 public function write_setting_flag(admin_setting $setting, $data) {
2190 if ($this->is_enabled()) {
2191 if (!isset($data)) {
2192 $value = $this->get_default();
2194 $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
2196 $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
2204 * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
2206 * @param admin_setting $setting - The admin setting for this flag
2207 * @return string - The html for the checkbox.
2209 public function output_setting_flag(admin_setting $setting) {
2212 $value = $setting->get_setting_flag_value($this);
2214 $context = new stdClass();
2215 $context->id = $setting->get_id() . '_' . $this->get_shortname();
2216 $context->name = $setting->get_full_name() . '_' . $this->get_shortname();
2217 $context->value = 1;
2218 $context->checked = $value ? true : false;
2219 $context->label = $this->get_displayname();
2221 return $OUTPUT->render_from_template('core_admin/setting_flag', $context);
2227 * No setting - just heading and text.
2229 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2231 class admin_setting_heading extends admin_setting {
2234 * not a setting, just text
2235 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2236 * @param string $heading heading
2237 * @param string $information text in box
2239 public function __construct($name, $heading, $information) {
2240 $this->nosave = true;
2241 parent::__construct($name, $heading, $information, '');
2245 * Always returns true
2246 * @return bool Always returns true
2248 public function get_setting() {
2253 * Always returns true
2254 * @return bool Always returns true
2256 public function get_defaultsetting() {
2261 * Never write settings
2262 * @return string Always returns an empty string
2264 public function write_setting($data) {
2265 // do not write any setting
2270 * Returns an HTML string
2271 * @return string Returns an HTML string
2273 public function output_html($data, $query='') {
2275 $context = new stdClass();
2276 $context->title = $this->visiblename;
2277 $context->description = $this->description;
2278 $context->descriptionformatted = highlight($query, markdown_to_html($this->description));
2279 return $OUTPUT->render_from_template('core_admin/setting_heading', $context);
2284 * No setting - just name and description in same row.
2286 * @copyright 2018 onwards Amaia Anabitarte
2287 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2289 class admin_setting_description extends admin_setting {
2292 * Not a setting, just text
2294 * @param string $name
2295 * @param string $visiblename
2296 * @param string $description
2298 public function __construct($name, $visiblename, $description) {
2299 $this->nosave = true;
2300 parent::__construct($name, $visiblename, $description, '');
2304 * Always returns true
2306 * @return bool Always returns true
2308 public function get_setting() {
2313 * Always returns true
2315 * @return bool Always returns true
2317 public function get_defaultsetting() {
2322 * Never write settings
2324 * @param mixed $data Gets converted to str for comparison against yes value
2325 * @return string Always returns an empty string
2327 public function write_setting($data) {
2328 // Do not write any setting.
2333 * Returns an HTML string
2335 * @param string $data
2336 * @param string $query
2337 * @return string Returns an HTML string
2339 public function output_html($data, $query='') {
2342 $context = new stdClass();
2343 $context->title = $this->visiblename;
2344 $context->description = $this->description;
2346 return $OUTPUT->render_from_template('core_admin/setting_description', $context);
2353 * The most flexible setting, the user enters text.
2355 * This type of field should be used for config settings which are using
2356 * English words and are not localised (passwords, database name, list of values, ...).
2358 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2360 class admin_setting_configtext extends admin_setting {
2362 /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
2364 /** @var int default field size */
2368 * Config text constructor
2370 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2371 * @param string $visiblename localised
2372 * @param string $description long localised info
2373 * @param string $defaultsetting
2374 * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2375 * @param int $size default field size
2377 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2378 $this->paramtype = $paramtype;
2379 if (!is_null($size)) {
2380 $this->size = $size;
2382 $this->size = ($paramtype === PARAM_INT) ? 5 : 30;
2384 parent::__construct($name, $visiblename, $description, $defaultsetting);
2388 * Get whether this should be displayed in LTR mode.
2390 * Try to guess from the PARAM type unless specifically set.
2392 public function get_force_ltr() {
2393 $forceltr = parent::get_force_ltr();
2394 if ($forceltr === null) {
2395 return !is_rtl_compatible($this->paramtype);
2401 * Return the setting
2403 * @return mixed returns config if successful else null
2405 public function get_setting() {
2406 return $this->config_read($this->name);
2409 public function write_setting($data) {
2410 if ($this->paramtype === PARAM_INT and $data === '') {
2411 // do not complain if '' used instead of 0
2414 // $data is a string
2415 $validated = $this->validate($data);
2416 if ($validated !== true) {
2419 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2423 * Validate data before storage
2424 * @param string data
2425 * @return mixed true if ok string if error found
2427 public function validate($data) {
2428 // allow paramtype to be a custom regex if it is the form of /pattern/
2429 if (preg_match('#^/.*/$#', $this->paramtype)) {
2430 if (preg_match($this->paramtype, $data)) {
2433 return get_string('validateerror', 'admin');
2436 } else if ($this->paramtype === PARAM_RAW) {
2440 $cleaned = clean_param($data, $this->paramtype);
2441 if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
2444 return get_string('validateerror', 'admin');
2450 * Return an XHTML string for the setting
2451 * @return string Returns an XHTML string
2453 public function output_html($data, $query='') {
2456 $default = $this->get_defaultsetting();
2457 $context = (object) [
2458 'size' => $this->size,
2459 'id' => $this->get_id(),
2460 'name' => $this->get_full_name(),
2462 'forceltr' => $this->get_force_ltr(),
2464 $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
2466 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2471 * Text input with a maximum length constraint.
2473 * @copyright 2015 onwards Ankit Agarwal
2474 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2476 class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
2478 /** @var int maximum number of chars allowed. */
2479 protected $maxlength;
2482 * Config text constructor
2484 * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2485 * or 'myplugin/mysetting' for ones in config_plugins.
2486 * @param string $visiblename localised
2487 * @param string $description long localised info
2488 * @param string $defaultsetting
2489 * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2490 * @param int $size default field size
2491 * @param mixed $maxlength int maxlength allowed, 0 for infinite.
2493 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW,
2494 $size=null, $maxlength = 0) {
2495 $this->maxlength = $maxlength;
2496 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
2500 * Validate data before storage
2502 * @param string $data data
2503 * @return mixed true if ok string if error found
2505 public function validate($data) {
2506 $parentvalidation = parent::validate($data);
2507 if ($parentvalidation === true) {
2508 if ($this->maxlength > 0) {
2509 // Max length check.
2510 $length = core_text::strlen($data);
2511 if ($length > $this->maxlength) {
2512 return get_string('maximumchars', 'moodle', $this->maxlength);
2516 return true; // No max length check needed.
2519 return $parentvalidation;
2525 * General text area without html editor.
2527 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2529 class admin_setting_configtextarea extends admin_setting_configtext {
2534 * @param string $name
2535 * @param string $visiblename
2536 * @param string $description
2537 * @param mixed $defaultsetting string or array
2538 * @param mixed $paramtype
2539 * @param string $cols The number of columns to make the editor
2540 * @param string $rows The number of rows to make the editor
2542 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2543 $this->rows = $rows;
2544 $this->cols = $cols;
2545 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
2549 * Returns an XHTML string for the editor
2551 * @param string $data
2552 * @param string $query
2553 * @return string XHTML string for the editor
2555 public function output_html($data, $query='') {
2558 $default = $this->get_defaultsetting();
2559 $defaultinfo = $default;
2560 if (!is_null($default) and $default !== '') {
2561 $defaultinfo = "\n".$default;
2564 $context = (object) [
2565 'cols' => $this->cols,
2566 'rows' => $this->rows,
2567 'id' => $this->get_id(),
2568 'name' => $this->get_full_name(),
2570 'forceltr' => $this->get_force_ltr(),
2572 $element = $OUTPUT->render_from_template('core_admin/setting_configtextarea', $context);
2574 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
2579 * General text area with html editor.
2581 class admin_setting_confightmleditor extends admin_setting_configtextarea {
2584 * @param string $name
2585 * @param string $visiblename
2586 * @param string $description
2587 * @param mixed $defaultsetting string or array
2588 * @param mixed $paramtype
2590 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2591 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows);
2592 $this->set_force_ltr(false);
2593 editors_head_setup();
2597 * Returns an XHTML string for the editor
2599 * @param string $data
2600 * @param string $query
2601 * @return string XHTML string for the editor
2603 public function output_html($data, $query='') {
2604 $editor = editors_get_preferred_editor(FORMAT_HTML);
2605 $editor->set_text($data);
2606 $editor->use_editor($this->get_id(), array('noclean'=>true));
2607 return parent::output_html($data, $query);
2611 * Checks if data has empty html.
2613 * @param string $data
2614 * @return string Empty when no errors.
2616 public function write_setting($data) {
2617 if (trim(html_to_text($data)) === '') {
2620 return parent::write_setting($data);
2626 * Password field, allows unmasking of password
2628 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2630 class admin_setting_configpasswordunmask extends admin_setting_configtext {
2634 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2635 * @param string $visiblename localised
2636 * @param string $description long localised info
2637 * @param string $defaultsetting default password
2639 public function __construct($name, $visiblename, $description, $defaultsetting) {
2640 parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2644 * Log config changes if necessary.
2645 * @param string $name
2646 * @param string $oldvalue
2647 * @param string $value
2649 protected function add_to_config_log($name, $oldvalue, $value) {
2650 if ($value !== '') {
2651 $value = '********';
2653 if ($oldvalue !== '' and $oldvalue !== null) {
2654 $oldvalue = '********';
2656 parent::add_to_config_log($name, $oldvalue, $value);
2660 * Returns HTML for the field.
2662 * @param string $data Value for the field
2663 * @param string $query Passed as final argument for format_admin_setting
2664 * @return string Rendered HTML
2666 public function output_html($data, $query='') {
2667 global $OUTPUT, $CFG;
2669 if (empty($this->plugin)) {
2670 if (array_key_exists($this->name, $CFG->config_php_settings)) {
2674 if (array_key_exists($this->plugin, $CFG->forced_plugin_settings)
2675 and array_key_exists($this->name, $CFG->forced_plugin_settings[$this->plugin])) {
2679 $context = (object) [
2680 'id' => $this->get_id(),
2681 'name' => $this->get_full_name(),
2682 'size' => $this->size,
2683 'value' => $forced ? null : $data,
2684 'forceltr' => $this->get_force_ltr(),
2687 $element = $OUTPUT->render_from_template('core_admin/setting_configpasswordunmask', $context);
2688 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', null, $query);
2693 * Password field, allows unmasking of password, with an advanced checkbox that controls an additional $name.'_adv' setting.
2695 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2696 * @copyright 2018 Paul Holden (pholden@greenhead.ac.uk)
2698 class admin_setting_configpasswordunmask_with_advanced extends admin_setting_configpasswordunmask {
2703 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2704 * @param string $visiblename localised
2705 * @param string $description long localised info
2706 * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
2708 public function __construct($name, $visiblename, $description, $defaultsetting) {
2709 parent::__construct($name, $visiblename, $description, $defaultsetting['value']);
2710 $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
2715 * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
2716 * Note: Only advanced makes sense right now - locked does not.
2718 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2720 class admin_setting_configempty extends admin_setting_configtext {
2723 * @param string $name
2724 * @param string $visiblename
2725 * @param string $description
2727 public function __construct($name, $visiblename, $description) {
2728 parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
2732 * Returns an XHTML string for the hidden field
2734 * @param string $data
2735 * @param string $query
2736 * @return string XHTML string for the editor
2738 public function output_html($data, $query='') {
2741 $context = (object) [
2742 'id' => $this->get_id(),
2743 'name' => $this->get_full_name()
2745 $element = $OUTPUT->render_from_template('core_admin/setting_configempty', $context);
2747 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', get_string('none'), $query);
2755 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2757 class admin_setting_configfile extends admin_setting_configtext {
2760 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2761 * @param string $visiblename localised
2762 * @param string $description long localised info
2763 * @param string $defaultdirectory default directory location
2765 public function __construct($name, $visiblename, $description, $defaultdirectory) {
2766 parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2770 * Returns XHTML for the field
2772 * Returns XHTML for the field and also checks whether the file
2773 * specified in $data exists using file_exists()
2775 * @param string $data File name and path to use in value attr
2776 * @param string $query
2777 * @return string XHTML field
2779 public function output_html($data, $query='') {
2780 global $CFG, $OUTPUT;
2782 $default = $this->get_defaultsetting();
2783 $context = (object) [
2784 'id' => $this->get_id(),
2785 'name' => $this->get_full_name(),
2786 'size' => $this->size,
2788 'showvalidity' => !empty($data),
2789 'valid' => $data && file_exists($data),
2790 'readonly' => !empty($CFG->preventexecpath),
2791 'forceltr' => $this->get_force_ltr(),
2794 if ($context->readonly) {
2795 $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2798 $element = $OUTPUT->render_from_template('core_admin/setting_configfile', $context);
2800 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2804 * Checks if execpatch has been disabled in config.php
2806 public function write_setting($data) {
2808 if (!empty($CFG->preventexecpath)) {
2809 if ($this->get_setting() === null) {
2810 // Use default during installation.
2811 $data = $this->get_defaultsetting();
2812 if ($data === null) {
2819 return parent::write_setting($data);
2826 * Path to executable file
2828 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2830 class admin_setting_configexecutable extends admin_setting_configfile {
2833 * Returns an XHTML field
2835 * @param string $data This is the value for the field
2836 * @param string $query
2837 * @return string XHTML field
2839 public function output_html($data, $query='') {
2840 global $CFG, $OUTPUT;
2841 $default = $this->get_defaultsetting();
2842 require_once("$CFG->libdir/filelib.php");
2844 $context = (object) [
2845 'id' => $this->get_id(),
2846 'name' => $this->get_full_name(),
2847 'size' => $this->size,
2849 'showvalidity' => !empty($data),
2850 'valid' => $data && file_exists($data) && !is_dir($data) && file_is_executable($data),
2851 'readonly' => !empty($CFG->preventexecpath),
2852 'forceltr' => $this->get_force_ltr()
2855 if (!empty($CFG->preventexecpath)) {
2856 $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2859 $element = $OUTPUT->render_from_template('core_admin/setting_configexecutable', $context);
2861 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2869 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2871 class admin_setting_configdirectory extends admin_setting_configfile {
2874 * Returns an XHTML field
2876 * @param string $data This is the value for the field
2877 * @param string $query
2878 * @return string XHTML
2880 public function output_html($data, $query='') {
2881 global $CFG, $OUTPUT;
2882 $default = $this->get_defaultsetting();
2884 $context = (object) [
2885 'id' => $this->get_id(),
2886 'name' => $this->get_full_name(),
2887 'size' => $this->size,
2889 'showvalidity' => !empty($data),
2890 'valid' => $data && file_exists($data) && is_dir($data),
2891 'readonly' => !empty($CFG->preventexecpath),
2892 'forceltr' => $this->get_force_ltr()
2895 if (!empty($CFG->preventexecpath)) {
2896 $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>';
2899 $element = $OUTPUT->render_from_template('core_admin/setting_configdirectory', $context);
2901 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query);
2909 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2911 class admin_setting_configcheckbox extends admin_setting {
2912 /** @var string Value used when checked */
2914 /** @var string Value used when not checked */
2919 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2920 * @param string $visiblename localised
2921 * @param string $description long localised info
2922 * @param string $defaultsetting
2923 * @param string $yes value used when checked
2924 * @param string $no value used when not checked
2926 public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2927 parent::__construct($name, $visiblename, $description, $defaultsetting);
2928 $this->yes = (string)$yes;
2929 $this->no = (string)$no;
2933 * Retrieves the current setting using the objects name
2937 public function get_setting() {
2938 return $this->config_read($this->name);
2942 * Sets the value for the setting
2944 * Sets the value for the setting to either the yes or no values
2945 * of the object by comparing $data to yes
2947 * @param mixed $data Gets converted to str for comparison against yes value
2948 * @return string empty string or error
2950 public function write_setting($data) {
2951 if ((string)$data === $this->yes) { // convert to strings before comparison
2956 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2960 * Returns an XHTML checkbox field
2962 * @param string $data If $data matches yes then checkbox is checked
2963 * @param string $query
2964 * @return string XHTML field
2966 public function output_html($data, $query='') {
2969 $context = (object) [
2970 'id' => $this->get_id(),
2971 'name' => $this->get_full_name(),
2973 'value' => $this->yes,
2974 'checked' => (string) $data === $this->yes,
2977 $default = $this->get_defaultsetting();
2978 if (!is_null($default)) {
2979 if ((string)$default === $this->yes) {
2980 $defaultinfo = get_string('checkboxyes', 'admin');
2982 $defaultinfo = get_string('checkboxno', 'admin');
2985 $defaultinfo = NULL;
2988 $element = $OUTPUT->render_from_template('core_admin/setting_configcheckbox', $context);
2990 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
2996 * Multiple checkboxes, each represents different value, stored in csv format
2998 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3000 class admin_setting_configmulticheckbox extends admin_setting {
3001 /** @var array Array of choices value=>label */
3005 * Constructor: uses parent::__construct
3007 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3008 * @param string $visiblename localised
3009 * @param string $description long localised info
3010 * @param array $defaultsetting array of selected
3011 * @param array $choices array of $value=>$label for each checkbox
3013 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3014 $this->choices = $choices;
3015 parent::__construct($name, $visiblename, $description, $defaultsetting);
3019 * This public function may be used in ancestors for lazy loading of choices
3021 * @todo Check if this function is still required content commented out only returns true
3022 * @return bool true if loaded, false if error
3024 public function load_choices() {
3026 if (is_array($this->choices)) {
3029 .... load choices here
3035 * Is setting related to query text - used when searching
3037 * @param string $query
3038 * @return bool true on related, false on not or failure
3040 public function is_related($query) {
3041 if (!$this->load_choices() or empty($this->choices)) {
3044 if (parent::is_related($query)) {
3048 foreach ($this->choices as $desc) {
3049 if (strpos(core_text::strtolower($desc), $query) !== false) {
3057 * Returns the current setting if it is set
3059 * @return mixed null if null, else an array
3061 public function get_setting() {
3062 $result = $this->config_read($this->name);
3064 if (is_null($result)) {
3067 if ($result === '') {
3070 $enabled = explode(',', $result);
3072 foreach ($enabled as $option) {
3073 $setting[$option] = 1;
3079 * Saves the setting(s) provided in $data
3081 * @param array $data An array of data, if not array returns empty str
3082 * @return mixed empty string on useless data or bool true=success, false=failed
3084 public function write_setting($data) {
3085 if (!is_array($data)) {
3086 return ''; // ignore it
3088 if (!$this->load_choices() or empty($this->choices)) {
3091 unset($data['xxxxx']);
3093 foreach ($data as $key => $value) {
3094 if ($value and array_key_exists($key, $this->choices)) {
3098 return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
3102 * Returns XHTML field(s) as required by choices
3104 * Relies on data being an array should data ever be another valid vartype with
3105 * acceptable value this may cause a warning/error
3106 * if (!is_array($data)) would fix the problem
3108 * @todo Add vartype handling to ensure $data is an array
3110 * @param array $data An array of checked values
3111 * @param string $query
3112 * @return string XHTML field
3114 public function output_html($data, $query='') {
3117 if (!$this->load_choices() or empty($this->choices)) {
3121 $default = $this->get_defaultsetting();
3122 if (is_null($default)) {
3125 if (is_null($data)) {
3129 $context = (object) [
3130 'id' => $this->get_id(),
3131 'name' => $this->get_full_name(),
3135 $defaults = array();
3136 foreach ($this->choices as $key => $description) {
3137 if (!empty($default[$key])) {
3138 $defaults[] = $description;
3143 'checked' => !empty($data[$key]),
3144 'label' => highlightfast($query, $description)
3148 if (is_null($default)) {
3149 $defaultinfo = null;
3150 } else if (!empty($defaults)) {
3151 $defaultinfo = implode(', ', $defaults);
3153 $defaultinfo = get_string('none');
3156 $context->options = $options;
3157 $context->hasoptions = !empty($options);
3159 $element = $OUTPUT->render_from_template('core_admin/setting_configmulticheckbox', $context);
3161 return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', $defaultinfo, $query);
3168 * Multiple checkboxes 2, value stored as string 00101011
3170 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3172 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
3175 * Returns the setting if set
3177 * @return mixed null if not set, else an array of set settings
3179 public function get_setting() {
3180 $result = $this->config_read($this->name);
3181 if (is_null($result)) {
3184 if (!$this->load_choices()) {
3187 $result = str_pad($result, count($this->choices), '0');
3188 $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
3190 foreach ($this->choices as $key=>$unused) {
3191 $value = array_shift($result);
3200 * Save setting(s) provided in $data param
3202 * @param array $data An array of settings to save
3203 * @return mixed empty string for bad data or bool true=>success, false=>error
3205 public function write_setting($data) {
3206 if (!is_array($data)) {
3207 return ''; // ignore it
3209 if (!$this->load_choices() or empty($this->choices)) {
3213 foreach ($this->choices as $key=>$unused) {
3214 if (!empty($data[$key])) {
3220 return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
3226 * Select one value from list
3228 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3230 class admin_setting_configselect extends admin_setting {
3231 /** @var array Array of choices value=>label */
3233 /** @var array Array of choices grouped using optgroups */
3235 /** @var callable|null Loader function for choices */
3236 protected $choiceloader = null;
3237 /** @var callable|null Validation function */
3238 protected $validatefunction = null;
3243 * If you want to lazy-load the choices, pass a callback function that returns a choice
3244 * array for the $choices parameter.
3246 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3247 * @param string $visiblename localised
3248 * @param string $description long localised info
3249 * @param string|int $defaultsetting
3250 * @param array|callable|null $choices array of $value=>$label for each selection, or callback
3252 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3253 // Look for optgroup and single options.
3254 if (is_array($choices)) {
3255 $this->choices = [];
3256 foreach ($choices as $key => $val) {
3257 if (is_array($val)) {
3258 $this->optgroups[$key] = $val;
3259 $this->choices = array_merge($this->choices, $val);
3261 $this->choices[$key] = $val;
3265 if (is_callable($choices)) {
3266 $this->choiceloader = $choices;
3269 parent::__construct($name, $visiblename, $description, $defaultsetting);
3273 * Sets a validate function.
3275 * The callback will be passed one parameter, the new setting value, and should return either
3276 * an empty string '' if the value is OK, or an error message if not.
3278 * @param callable|null $validatefunction Validate function or null to clear
3279 * @since Moodle 3.10
3281 public function set_validate_function(?callable $validatefunction = null) {
3282 $this->validatefunction = $validatefunction;
3286 * This function may be used in ancestors for lazy loading of choices
3288 * Override this method if loading of choices is expensive, such
3289 * as when it requires multiple db requests.
3291 * @return bool true if loaded, false if error
3293 public function load_choices() {
3294 if ($this->choiceloader) {
3295 if (!is_array($this->choices)) {
3296 $this->choices = call_user_func($this->choiceloader);
3304 * Check if this is $query is related to a choice
3306 * @param string $query
3307 * @return bool true if related, false if not
3309 public function is_related($query) {
3310 if (parent::is_related($query)) {
3313 if (!$this->load_choices()) {
3316 foreach ($this->choices as $key=>$value) {
3317 if (strpos(core_text::strtolower($key), $query) !== false) {
3320 if (strpos(core_text::strtolower($value), $query) !== false) {
3328 * Return the setting
3330 * @return mixed returns config if successful else null
3332 public function get_setting() {
3333 return $this->config_read($this->name);
3339 * @param string $data
3340 * @return string empty of error string
3342 public function write_setting($data) {
3343 if (!$this->load_choices() or empty($this->choices)) {
3346 if (!array_key_exists($data, $this->choices)) {
3347 return ''; // ignore it
3350 // Validate the new setting.
3351 $error = $this->validate_setting($data);
3356 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3360 * Validate the setting. This uses the callback function if provided; subclasses could override
3361 * to carry out validation directly in the class.
3363 * @param string $data New value being set
3364 * @return string Empty string if valid, or error message text
3365 * @since Moodle 3.10
3367 protected function validate_setting(string $data): string {
3368 // If validation function is specified, call it now.
3369 if ($this->validatefunction) {
3370 return call_user_func($this->validatefunction, $data);
3377 * Returns XHTML select field
3379 * Ensure the options are loaded, and generate the XHTML for the select
3380 * element and any warning message. Separating this out from output_html
3381 * makes it easier to subclass this class.
3383 * @param string $data the option to show as selected.
3384 * @param string $current the currently selected option in the database, null if none.
3385 * @param string $default the default selected option.
3386 * @return array the HTML for the select element, and a warning message.
3387 * @deprecated since Moodle 3.2
3389 public function output_select_html($data, $current, $default, $extraname = '') {
3390 debugging('The method admin_setting_configselect::output_select_html is depreacted, do not use any more.', DEBUG_DEVELOPER);
3394 * Returns XHTML select field and wrapping div(s)
3396 * @see output_select_html()
3398 * @param string $data the option to show as selected
3399 * @param string $query
3400 * @return string XHTML field and wrapping div
3402 public function output_html($data, $query='') {
3405 $default = $this->get_defaultsetting();
3406 $current = $this->get_setting();
3408 if (!$this->load_choices() || empty($this->choices)) {
3412 $context = (object) [
3413 'id' => $this->get_id(),
3414 'name' => $this->get_full_name(),
3417 if (!is_null($default) && array_key_exists($default, $this->choices)) {
3418 $defaultinfo = $this->choices[$default];
3420 $defaultinfo = NULL;
3425 if ($current === null) {
3427 } else if (empty($current) && (array_key_exists('', $this->choices) || array_key_exists(0, $this->choices))) {
3429 } else if (!array_key_exists($current, $this->choices)) {
3430 $warning = get_string('warningcurrentsetting', 'admin', $current);
3431 if (!is_null($default) && $data == $current) {
3432 $data = $default; // Use default instead of first value when showing the form.
3437 $template = 'core_admin/setting_configselect';
3439 if (!empty($this->optgroups)) {
3441 foreach ($this->optgroups as $label => $choices) {
3442 $optgroup = array('label' => $label, 'options' => []);
3443 foreach ($choices as $value => $name) {
3444 $optgroup['options'][] = [
3447 'selected' => (string) $value == $data
3449 unset($this->choices[$value]);
3451 $optgroups[] = $optgroup;
3453 $context->options = $options;
3454 $context->optgroups = $optgroups;
3455 $template = 'core_admin/setting_configselect_optgroup';
3458 foreach ($this->choices as $value => $name) {
3462 'selected' => (string) $value == $data
3465 $context->options = $options;
3467 $element = $OUTPUT->render_from_template($template, $context);
3469 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, $warning, $defaultinfo, $query);
3474 * Select multiple items from list
3476 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3478 class admin_setting_configmultiselect extends admin_setting_configselect {
3481 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3482 * @param string $visiblename localised
3483 * @param string $description long localised info
3484 * @param array $defaultsetting array of selected items
3485 * @param array $choices array of $value=>$label for each list item
3487 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3488 parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
3492 * Returns the select setting(s)
3494 * @return mixed null or array. Null if no settings else array of setting(s)
3496 public function get_setting() {
3497 $result = $this->config_read($this->name);
3498 if (is_null($result)) {
3501 if ($result === '') {
3504 return explode(',', $result);
3508 * Saves setting(s) provided through $data
3510 * Potential bug in the works should anyone call with this function
3511 * using a vartype that is not an array
3513 * @param array $data
3515 public function write_setting($data) {
3516 if (!is_array($data)) {
3517 return ''; //ignore it
3519 if (!$this->load_choices() or empty($this->choices)) {
3523 unset($data['xxxxx']);
3526 foreach ($data as $value) {
3527 if (!array_key_exists($value, $this->choices)) {
3528 continue; // ignore it
3533 return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3537 * Is setting related to query text - used when searching
3539 * @param string $query
3540 * @return bool true if related, false if not
3542 public function is_related($query) {
3543 if (!$this->load_choices() or empty($this->choices)) {
3546 if (parent::is_related($query)) {
3550 foreach ($this->choices as $desc) {
3551 if (strpos(core_text::strtolower($desc), $query) !== false) {
3559 * Returns XHTML multi-select field
3561 * @todo Add vartype handling to ensure $data is an array
3562 * @param array $data Array of values to select by default
3563 * @param string $query
3564 * @return string XHTML multi-select field
3566 public function output_html($data, $query='') {
3569 if (!$this->load_choices() or empty($this->choices)) {
3573 $default = $this->get_defaultsetting();
3574 if (is_null($default)) {
3577 if (is_null($data)) {
3581 $context = (object) [
3582 'id' => $this->get_id(),
3583 'name' => $this->get_full_name(),
3584 'size' => min(10, count($this->choices))
3589 $template = 'core_admin/setting_configmultiselect';
3591 if (!empty($this->optgroups)) {
3593 foreach ($this->optgroups as $label => $choices) {
3594 $optgroup = array('label' => $label, 'options' => []);
3595 foreach ($choices as $value => $name) {
3596 if (in_array($value, $default)) {
3597 $defaults[] = $name;
3599 $optgroup['options'][] = [
3602 'selected' => in_array($value, $data)
3604 unset($this->choices[$value]);
3606 $optgroups[] = $optgroup;
3608 $context->optgroups = $optgroups;
3609 $template = 'core_admin/setting_configmultiselect_optgroup';
3612 foreach ($this->choices as $value => $name) {
3613 if (in_array($value, $default)) {
3614 $defaults[] = $name;
3619 'selected' => in_array($value, $data)
3622 $context->options = $options;
3624 if (is_null($default)) {
3625 $defaultinfo = NULL;
3626 } if (!empty($defaults)) {
3627 $defaultinfo = implode(', ', $defaults);
3629 $defaultinfo = get_string('none');
3632 $element = $OUTPUT->render_from_template($template, $context);
3634 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query);
3641 * This is a liiitle bit messy. we're using two selects, but we're returning
3642 * them as an array named after $name (so we only use $name2 internally for the setting)
3644 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3646 class admin_setting_configtime extends admin_setting {
3647 /** @var string Used for setting second select (minutes) */
3652 * @param string $hoursname setting for hours
3653 * @param string $minutesname setting for hours
3654 * @param string $visiblename localised
3655 * @param string $description long localised info
3656 * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3658 public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
3659 $this->name2 = $minutesname;
3660 parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
3664 * Get the selected time
3666 * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
3668 public function get_setting() {
3669 $result1 = $this->config_read($this->name);
3670 $result2 = $this->config_read($this->name2);
3671 if (is_null($result1) or is_null($result2)) {
3675 return array('h' => $result1, 'm' => $result2);
3679 * Store the time (hours and minutes)
3681 * @param array $data Must be form 'h'=>xx, 'm'=>xx
3682 * @return bool true if success, false if not
3684 public function write_setting($data) {
3685 if (!is_array($data)) {
3689 $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3690 return ($result ? '' : get_string('errorsetting', 'admin'));
3694 * Returns XHTML time select fields
3696 * @param array $data Must be form 'h'=>xx, 'm'=>xx
3697 * @param string $query
3698 * @return string XHTML time select fields and wrapping div(s)
3700 public function output_html($data, $query='') {
3703 $default = $this->get_defaultsetting();
3704 if (is_array($default)) {
3705 $defaultinfo = $default['h'].':'.$default['m'];
3707 $defaultinfo = NULL;
3710 $context = (object) [
3711 'id' => $this->get_id(),
3712 'name' => $this->get_full_name(),
3713 'hours' => array_map(function($i) use ($data) {
3717 'selected' => $i == $data['h']
3720 'minutes' => array_map(function($i) use ($data) {
3724 'selected' => $i == $data['m']
3729 $element = $OUTPUT->render_from_template('core_admin/setting_configtime', $context);
3731 return format_admin_setting($this, $this->visiblename, $element, $this->description,
3732 $this->get_id() . 'h', '', $defaultinfo, $query);
3739 * Seconds duration setting.
3741 * @copyright 2012 Petr Skoda (http://skodak.org)
3742 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3744 class admin_setting_configduration extends admin_setting {
3746 /** @var int default duration unit */
3747 protected $defaultunit;
3751 * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3752 * or 'myplugin/mysetting' for ones in config_plugins.
3753 * @param string $visiblename localised name
3754 * @param string $description localised long description
3755 * @param mixed $defaultsetting string or array depending on implementation
3756 * @param int $defaultunit - day, week, etc. (in seconds)
3758 public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3759 if (is_number($defaultsetting)) {
3760 $defaultsetting = self::parse_seconds($defaultsetting);
3762 $units = self::get_units();
3763 if (isset($units[$defaultunit])) {
3764 $this->defaultunit = $defaultunit;
3766 $this->defaultunit = 86400;
3768 parent::__construct($name, $visiblename, $description, $defaultsetting);
3772 * Returns selectable units.
3776 protected static function get_units() {
3778 604800 => get_string('weeks'),
3779 86400 => get_string('days'),
3780 3600 => get_string('hours'),
3781 60 => get_string('minutes'),
3782 1 => get_string('seconds'),
3787 * Converts seconds to some more user friendly string.
3789 * @param int $seconds
3792 protected static function get_duration_text($seconds) {
3793 if (empty($seconds)) {
3794 return get_string('none');
3796 $data = self::parse_seconds($seconds);
3797 switch ($data['u']) {
3799 return get_string('numweeks', '', $data['v']);
3801 return get_string('numdays', '', $data['v']);
3803 return get_string('numhours', '', $data['v']);
3805 return get_string('numminutes', '', $data['v']);
3807 return get_string('numseconds', '', $data['v']*$data['u']);
3812 * Finds suitable units for given duration.
3814 * @param int $seconds
3817 protected static function parse_seconds($seconds) {
3818 foreach (self::get_units() as $unit => $unused) {
3819 if ($seconds % $unit === 0) {
3820 return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
3823 return array('v'=>(int)$seconds, 'u'=>1);
3827 * Get the selected duration as array.
3829 * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
3831 public function get_setting() {
3832 $seconds = $this->config_read($this->name);
3833 if (is_null($seconds)) {
3837 return self::parse_seconds($seconds);
3841 * Store the duration as seconds.
3843 * @param array $data Must be form 'h'=>xx, 'm'=>xx
3844 * @return bool true if success, false if not
3846 public function write_setting($data) {
3847 if (!is_array($data)) {
3851 $seconds = (int)($data['v']*$data['u']);
3853 return get_string('errorsetting', 'admin');
3856 $result = $this->config_write($this->name, $seconds);
3857 return ($result ? '' : get_string('errorsetting', 'admin'));
3861 * Returns duration text+select fields.
3863 * @param array $data Must be form 'v'=>xx, 'u'=>xx
3864 * @param string $query
3865 * @return string duration text+select fields and wrapping div(s)
3867 public function output_html($data, $query='') {
3870 $default = $this->get_defaultsetting();
3871 if (is_number($default)) {
3872 $defaultinfo = self::get_duration_text($default);
3873 } else if (is_array($default)) {
3874 $defaultinfo = self::get_duration_text($default['v']*$default['u']);
3876 $defaultinfo = null;
3879 $inputid = $this->get_id() . 'v';
3880 $units = self::get_units();
3881 $defaultunit = $this->defaultunit;
3883 $context = (object) [
3884 'id' => $this->get_id(),
3885 'name' => $this->get_full_name(),
3886 'value' => $data['v'],
3887 'options' => array_map(function($unit) use ($units, $data, $defaultunit) {
3890 'name' => $units[$unit],
3891 'selected' => ($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u']
3893 }, array_keys($units))
3896 $element = $OUTPUT->render_from_template('core_admin/setting_configduration', $context);
3898 return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
3904 * Seconds duration setting with an advanced checkbox, that controls a additional
3905 * $name.'_adv' setting.
3907 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3908 * @copyright 2014 The Open University
3910 class admin_setting_configduration_with_advanced extends admin_setting_configduration {
3913 * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3914 * or 'myplugin/mysetting' for ones in config_plugins.
3915 * @param string $visiblename localised name
3916 * @param string $description localised long description
3917 * @param array $defaultsetting array of int value, and bool whether it is
3918 * is advanced by default.
3919 * @param int $defaultunit - day, week, etc. (in seconds)
3921 public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3922 parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $defaultunit);
3923 $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv']));
3929 * Used to validate a textarea used for ip addresses
3931 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3932 * @copyright 2011 Petr Skoda (http://skodak.org)
3934 class admin_setting_configiplist extends admin_setting_configtextarea {
3937 * Validate the contents of the textarea as IP addresses
3939 * Used to validate a new line separated list of IP addresses collected from
3940 * a textarea control
3942 * @param string $data A list of IP Addresses separated by new lines
3943 * @return mixed bool true for success or string:error on failure
3945 public function validate($data) {
3947 $lines = explode("\n", $data);
3953 foreach ($lines as $line) {
3954 $tokens = explode('#', $line);
3955 $ip = trim($tokens[0]);
3959 if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
3960 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
3961 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
3970 return get_string('validateiperror', 'admin', join(', ', $badips));
3976 * Used to validate a textarea used for domain names, wildcard domain names and IP addresses/ranges (both IPv4 and IPv6 format).
3978 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3979 * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
3981 class admin_setting_configmixedhostiplist extends admin_setting_configtextarea {
3984 * Validate the contents of the textarea as either IP addresses, domain name or wildcard domain name (RFC 4592).
3985 * Used to validate a new line separated list of entries collected from a textarea control.
3987 * This setting provides support for internationalised domain names (IDNs), however, such UTF-8 names will be converted to
3988 * their ascii-compatible encoding (punycode) on save, and converted back to their UTF-8 representation when fetched
3989 * via the get_setting() method, which has been overriden.
3991 * @param string $data A list of FQDNs, DNS wildcard format domains, and IP addresses, separated by new lines.
3992 * @return mixed bool true for success or string:error on failure
3994 public function validate($data) {
3998 $entries = explode("\n", $data);
4001 foreach ($entries as $key => $entry) {
4002 $entry = trim($entry);
4003 if (empty($entry)) {
4004 return get_string('validateemptylineerror', 'admin');
4007 // Validate each string entry against the supported formats.
4008 if (\core\ip_utils::is_ip_address($entry) || \core\ip_utils::is_ipv6_range($entry)
4009 || \core\ip_utils::is_ipv4_range($entry) || \core\ip_utils::is_domain_name($entry)
4010 || \core\ip_utils::is_domain_matching_pattern($entry)) {
4014 // Otherwise, the entry is invalid.
4015 $badentries[] = $entry;
4019 return get_string('validateerrorlist', 'admin', join(', ', $badentries));
4025 * Convert any lines containing international domain names (IDNs) to their ascii-compatible encoding (ACE).
4027 * @param string $data the setting data, as sent from the web form.
4028 * @return string $data the setting data, with all IDNs converted (using punycode) to their ascii encoded version.
4030 protected function ace_encode($data) {
4034 $entries = explode("\n", $data);
4035 foreach ($entries as $key => $entry) {
4036 $entry = trim($entry);
4037 // This regex matches any string that has non-ascii character.
4038 if (preg_match('/[^\x00-\x7f]/', $entry)) {
4039 // If we can convert the unicode string to an idn, do so.
4040 // Otherwise, leave the original unicode string alone and let the validation function handle it (it will fail).
4041 $val = idn_to_ascii($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4042 $entries[$key] = $val ? $val : $entry;
4045 return implode("\n", $entries);
4049 * Decode any ascii-encoded domain names back to their utf-8 representation for display.
4051 * @param string $data the setting data, as found in the database.
4052 * @return string $data the setting data, with all ascii-encoded IDNs decoded back to their utf-8 representation.
4054 protected function ace_decode($data) {
4055 $entries = explode("\n", $data);
4056 foreach ($entries as $key => $entry) {
4057 $entry = trim($entry);
4058 if (strpos($entry, 'xn--') !== false) {
4059 $entries[$key] = idn_to_utf8($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
4062 return implode("\n", $entries);
4066 * Override, providing utf8-decoding for ascii-encoded IDN strings.
4068 * @return mixed returns punycode-converted setting string if successful, else null.
4070 public function get_setting() {
4071 // Here, we need to decode any ascii-encoded IDNs back to their native, utf-8 representation.
4072 $data = $this->config_read($this->name);
4073 if (function_exists('idn_to_utf8') && !is_null($data)) {
4074 $data = $this->ace_decode($data);
4080 * Override, providing ascii-encoding for utf8 (native) IDN strings.
4082 * @param string $data
4085 public function write_setting($data) {
4086 if ($this->paramtype === PARAM_INT and $data === '') {
4087 // Do not complain if '' used instead of 0.
4091 // Try to convert any non-ascii domains to ACE prior to validation - we can't modify anything in validate!
4092 if (function_exists('idn_to_ascii')) {
4093 $data = $this->ace_encode($data);
4096 $validated = $this->validate($data);
4097 if ($validated !== true) {
4100 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
4105 * Used to validate a textarea used for port numbers.
4107 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4108 * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com)
4110 class admin_setting_configportlist extends admin_setting_configtextarea {
4113 * Validate the contents of the textarea as port numbers.
4114 * Used to validate a new line separated list of ports collected from a textarea control.
4116 * @param string $data A list of ports separated by new lines
4117 * @return mixed bool true for success or string:error on failure
4119 public function validate($data) {
4123 $ports = explode("\n", $data);
4125 foreach ($ports as $port) {
4126 $port = trim($port);
4128 return get_string('validateemptylineerror', 'admin');
4131 // Is the string a valid integer number?
4132 if (strval(intval($port)) !== $port || intval($port) <= 0) {
4133 $badentries[] = $port;
4137 return get_string('validateerrorlist', 'admin', $badentries);
4145 * An admin setting for selecting one or more users who have a capability
4146 * in the system context
4148 * An admin setting for selecting one or more users, who have a particular capability
4149 * in the system context. Warning, make sure the list will never be too long. There is
4150 * no paging or searching of this list.
4152 * To correctly get a list of users from this config setting, you need to call the
4153 * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
4155 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4157 class admin_setting_users_with_capability extends admin_setting_configmultiselect {
4158 /** @var string The capabilities name */
4159 protected $capability;
4160 /** @var int include admin users too */
4161 protected $includeadmins;
4166 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
4167 * @param string $visiblename localised name
4168 * @param string $description localised long description
4169 * @param array $defaultsetting array of usernames
4170 * @param string $capability string capability name.
4171 * @param bool $includeadmins include administrators
4173 function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
4174 $this->capability = $capability;
4175 $this->includeadmins = $includeadmins;
4176 parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
4180 * Load all of the uses who have the capability into choice array
4182 * @return bool Always returns true
4184 function load_choices() {
4185 if (is_array($this->choices)) {
4188 list($sort, $sortparams) = users_order_by_sql('u');
4189 if (!empty($sortparams)) {
4190 throw new coding_exception('users_order_by_sql returned some query parameters. ' .
4191 'This is unexpected, and a problem because there is no way to pass these ' .
4192 'parameters to get_users_by_capability. See MDL-34657.');
4194 $userfields = 'u.id, u.username, ' . get_all_user_name_fields(true, 'u');
4195 $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort);
4196 $this->choices = array(
4197 '$@NONE@$' => get_string('nobody'),
4198 '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
4200 if ($this->includeadmins) {
4201 $admins = get_admins();
4202 foreach ($admins as $user) {
4203 $this->choices[$user->id] = fullname($user);
4206 if (is_array($users)) {
4207 foreach ($users as $user) {
4208 $this->choices[$user->id] = fullname($user);
4215 * Returns the default setting for class
4217 * @return mixed Array, or string. Empty string if no default
4219 public function get_defaultsetting() {
4220 $this->load_choices();
4221 $defaultsetting = parent::get_defaultsetting();
4222 if (empty($defaultsetting)) {
4223 return array('$@NONE@$');
4224 } else if (array_key_exists($defaultsetting, $this->choices)) {
4225 return $defaultsetting;
4232 * Returns the current setting
4234 * @return mixed array or string
4236 public function get_setting() {
4237 $result = parent::get_setting();
4238 if ($result === null) {
4239 // this is necessary for settings upgrade
4242 if (empty($result)) {
4243 $result = array('$@NONE@$');
4249 * Save the chosen setting provided as $data
4251 * @param array $data
4252 * @return mixed string or array
4254 public function write_setting($data) {
4255 // If all is selected, remove any explicit options.
4256 if (in_array('$@ALL@$', $data)) {
4257 $data = array('$@ALL@$');
4259 // None never needs to be written to the DB.
4260 if (in_array('$@NONE@$', $data)) {
4261 unset($data[array_search('$@NONE@$', $data)]);
4263 return parent::write_setting($data);
4269 * Special checkbox for calendar - resets SESSION vars.
4271 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4273 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
4275 * Calls the parent::__construct with default values
4277 * name => calendar_adminseesall
4278 * visiblename => get_string('adminseesall', 'admin')
4279 * description => get_string('helpadminseesall', 'admin')
4280 * defaultsetting => 0
4282 public function __construct() {
4283 parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
4284 get_string('helpadminseesall', 'admin'), '0');
4288 * Stores the setting passed in $data
4290 * @param mixed gets converted to string for comparison
4291 * @return string empty string or error message
4293 public function write_setting($data) {
4295 return parent::write_setting($data);
4300 * Special select for settings that are altered in setup.php and can not be altered on the fly
4302 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4304 class admin_setting_special_selectsetup extends admin_setting_configselect {
4306 * Reads the setting directly from the database
4310 public function get_setting() {
4311 // read directly from db!
4312 return get_config(NULL, $this->name);
4316 * Save the setting passed in $data
4318 * @param string $data The setting to save
4319 * @return string empty or error message
4321 public function write_setting($data) {
4323 // do not change active CFG setting!
4324 $current = $CFG->{$this->name};
4325 $result = parent::write_setting($data);
4326 $CFG->{$this->name} = $current;
4333 * Special select for frontpage - stores data in course table
4335 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4337 class admin_setting_sitesetselect extends admin_setting_configselect {
4339 * Returns the site name for the selected site
4342 * @return string The site name of the selected site
4344 public function get_setting() {
4345 $site = course_get_format(get_site())->get_course();
4346 return $site->{$this->name};
4350 * Updates the database and save the setting