Revert "MDL-39876 update_course_record function to update global COURSE"
[moodle.git] / lib / adminlib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Functions and classes used during installation, upgrades and for admin settings.
19  *
20  *  ADMIN SETTINGS TREE INTRODUCTION
21  *
22  *  This file performs the following tasks:
23  *   -it defines the necessary objects and interfaces to build the Moodle
24  *    admin hierarchy
25  *   -it defines the admin_externalpage_setup()
26  *
27  *  ADMIN_SETTING OBJECTS
28  *
29  *  Moodle settings are represented by objects that inherit from the admin_setting
30  *  class. These objects encapsulate how to read a setting, how to write a new value
31  *  to a setting, and how to appropriately display the HTML to modify the setting.
32  *
33  *  ADMIN_SETTINGPAGE OBJECTS
34  *
35  *  The admin_setting objects are then grouped into admin_settingpages. The latter
36  *  appear in the Moodle admin tree block. All interaction with admin_settingpage
37  *  objects is handled by the admin/settings.php file.
38  *
39  *  ADMIN_EXTERNALPAGE OBJECTS
40  *
41  *  There are some settings in Moodle that are too complex to (efficiently) handle
42  *  with admin_settingpages. (Consider, for example, user management and displaying
43  *  lists of users.) In this case, we use the admin_externalpage object. This object
44  *  places a link to an external PHP file in the admin tree block.
45  *
46  *  If you're using an admin_externalpage object for some settings, you can take
47  *  advantage of the admin_externalpage_* functions. For example, suppose you wanted
48  *  to add a foo.php file into admin. First off, you add the following line to
49  *  admin/settings/first.php (at the end of the file) or to some other file in
50  *  admin/settings:
51  * <code>
52  *     $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
53  *         $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
54  * </code>
55  *
56  *  Next, in foo.php, your file structure would resemble the following:
57  * <code>
58  *         require(dirname(dirname(dirname(__FILE__))).'/config.php');
59  *         require_once($CFG->libdir.'/adminlib.php');
60  *         admin_externalpage_setup('foo');
61  *         // functionality like processing form submissions goes here
62  *         echo $OUTPUT->header();
63  *         // your HTML goes here
64  *         echo $OUTPUT->footer();
65  * </code>
66  *
67  *  The admin_externalpage_setup() function call ensures the user is logged in,
68  *  and makes sure that they have the proper role permission to access the page.
69  *  It also configures all $PAGE properties needed for navigation.
70  *
71  *  ADMIN_CATEGORY OBJECTS
72  *
73  *  Above and beyond all this, we have admin_category objects. These objects
74  *  appear as folders in the admin tree block. They contain admin_settingpage's,
75  *  admin_externalpage's, and other admin_category's.
76  *
77  *  OTHER NOTES
78  *
79  *  admin_settingpage's, admin_externalpage's, and admin_category's all inherit
80  *  from part_of_admin_tree (a pseudointerface). This interface insists that
81  *  a class has a check_access method for access permissions, a locate method
82  *  used to find a specific node in the admin tree and find parent path.
83  *
84  *  admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
85  *  interface ensures that the class implements a recursive add function which
86  *  accepts a part_of_admin_tree object and searches for the proper place to
87  *  put it. parentable_part_of_admin_tree implies part_of_admin_tree.
88  *
89  *  Please note that the $this->name field of any part_of_admin_tree must be
90  *  UNIQUE throughout the ENTIRE admin tree.
91  *
92  *  The $this->name field of an admin_setting object (which is *not* part_of_
93  *  admin_tree) must be unique on the respective admin_settingpage where it is
94  *  used.
95  *
96  * Original author: Vincenzo K. Marcovecchio
97  * Maintainer:      Petr Skoda
98  *
99  * @package    core
100  * @subpackage admin
101  * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
102  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
103  */
105 defined('MOODLE_INTERNAL') || die();
107 /// Add libraries
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);
115 /**
116  * Automatically clean-up all plugin data and remove the plugin DB tables
117  *
118  * NOTE: do not call directly, use new /admin/plugins.php?uninstall=component instead!
119  *
120  * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
121  * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
122  * @uses global $OUTPUT to produce notices and other messages
123  * @return void
124  */
125 function uninstall_plugin($type, $name) {
126     global $CFG, $DB, $OUTPUT;
128     // This may take a long time.
129     @set_time_limit(0);
131     // Recursively uninstall all subplugins first.
132     $subplugintypes = core_component::get_plugin_types_with_subplugins();
133     if (isset($subplugintypes[$type])) {
134         $base = core_component::get_plugin_directory($type, $name);
135         if (file_exists("$base/db/subplugins.php")) {
136             $subplugins = array();
137             include("$base/db/subplugins.php");
138             foreach ($subplugins as $subplugintype=>$dir) {
139                 $instances = core_component::get_plugin_list($subplugintype);
140                 foreach ($instances as $subpluginname => $notusedpluginpath) {
141                     uninstall_plugin($subplugintype, $subpluginname);
142                 }
143             }
144         }
146     }
148     $component = $type . '_' . $name;  // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
150     if ($type === 'mod') {
151         $pluginname = $name;  // eg. 'forum'
152         if (get_string_manager()->string_exists('modulename', $component)) {
153             $strpluginname = get_string('modulename', $component);
154         } else {
155             $strpluginname = $component;
156         }
158     } else {
159         $pluginname = $component;
160         if (get_string_manager()->string_exists('pluginname', $component)) {
161             $strpluginname = get_string('pluginname', $component);
162         } else {
163             $strpluginname = $component;
164         }
165     }
167     echo $OUTPUT->heading($pluginname);
169     $plugindirectory = core_component::get_plugin_directory($type, $name);
170     $uninstalllib = $plugindirectory . '/db/uninstall.php';
171     if (file_exists($uninstalllib)) {
172         require_once($uninstalllib);
173         $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall';    // eg. 'xmldb_workshop_uninstall()'
174         if (function_exists($uninstallfunction)) {
175             if (!$uninstallfunction()) {
176                 echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $pluginname);
177             }
178         }
179     }
181     if ($type === 'mod') {
182         // perform cleanup tasks specific for activity modules
184         if (!$module = $DB->get_record('modules', array('name' => $name))) {
185             print_error('moduledoesnotexist', 'error');
186         }
188         // delete all the relevant instances from all course sections
189         if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) {
190             foreach ($coursemods as $coursemod) {
191                 if (!delete_mod_from_section($coursemod->id, $coursemod->section)) {
192                     echo $OUTPUT->notification("Could not delete the $strpluginname with id = $coursemod->id from section $coursemod->section");
193                 }
194             }
195         }
197         // clear course.modinfo for courses that used this module
198         $sql = "UPDATE {course}
199                    SET modinfo=''
200                  WHERE id IN (SELECT DISTINCT course
201                                 FROM {course_modules}
202                                WHERE module=?)";
203         $DB->execute($sql, array($module->id));
205         // delete all the course module records
206         $DB->delete_records('course_modules', array('module' => $module->id));
208         // delete module contexts
209         if ($coursemods) {
210             foreach ($coursemods as $coursemod) {
211                 context_helper::delete_instance(CONTEXT_MODULE, $coursemod->id);
212             }
213         }
215         // delete the module entry itself
216         $DB->delete_records('modules', array('name' => $module->name));
218         // cleanup the gradebook
219         require_once($CFG->libdir.'/gradelib.php');
220         grade_uninstalled_module($module->name);
222         // Perform any custom uninstall tasks
223         if (file_exists($CFG->dirroot . '/mod/' . $module->name . '/lib.php')) {
224             require_once($CFG->dirroot . '/mod/' . $module->name . '/lib.php');
225             $uninstallfunction = $module->name . '_uninstall';
226             if (function_exists($uninstallfunction)) {
227                 debugging("{$uninstallfunction}() has been deprecated. Use the plugin's db/uninstall.php instead", DEBUG_DEVELOPER);
228                 if (!$uninstallfunction()) {
229                     echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $module->name.'!');
230                 }
231             }
232         }
234     } else if ($type === 'enrol') {
235         // NOTE: this is a bit brute force way - it will not trigger events and hooks properly
236         // nuke all role assignments
237         role_unassign_all(array('component'=>$component));
238         // purge participants
239         $DB->delete_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($name));
240         // purge enrol instances
241         $DB->delete_records('enrol', array('enrol'=>$name));
242         // tweak enrol settings
243         if (!empty($CFG->enrol_plugins_enabled)) {
244             $enabledenrols = explode(',', $CFG->enrol_plugins_enabled);
245             $enabledenrols = array_unique($enabledenrols);
246             $enabledenrols = array_flip($enabledenrols);
247             unset($enabledenrols[$name]);
248             $enabledenrols = array_flip($enabledenrols);
249             if (is_array($enabledenrols)) {
250                 set_config('enrol_plugins_enabled', implode(',', $enabledenrols));
251             }
252         }
254     } else if ($type === 'block') {
255         if ($block = $DB->get_record('block', array('name'=>$name))) {
256             // Inform block it's about to be deleted
257             if (file_exists("$CFG->dirroot/blocks/$block->name/block_$block->name.php")) {
258                 $blockobject = block_instance($block->name);
259                 if ($blockobject) {
260                     $blockobject->before_delete();  //only if we can create instance, block might have been already removed
261                 }
262             }
264             // First delete instances and related contexts
265             $instances = $DB->get_records('block_instances', array('blockname' => $block->name));
266             foreach($instances as $instance) {
267                 blocks_delete_instance($instance);
268             }
270             // Delete block
271             $DB->delete_records('block', array('id'=>$block->id));
272         }
273     } else if ($type === 'format') {
274         if (($defaultformat = get_config('moodlecourse', 'format')) && $defaultformat !== $name) {
275             $courses = $DB->get_records('course', array('format' => $name), 'id');
276             $data = (object)array('id' => null, 'format' => $defaultformat);
277             foreach ($courses as $record) {
278                 $data->id = $record->id;
279                 update_course($data);
280             }
281         }
282         $DB->delete_records('course_format_options', array('format' => $name));
283     }
285     // perform clean-up task common for all the plugin/subplugin types
287     //delete the web service functions and pre-built services
288     require_once($CFG->dirroot.'/lib/externallib.php');
289     external_delete_descriptions($component);
291     // delete calendar events
292     $DB->delete_records('event', array('modulename' => $pluginname));
294     // delete all the logs
295     $DB->delete_records('log', array('module' => $pluginname));
297     // delete log_display information
298     $DB->delete_records('log_display', array('component' => $component));
300     // delete the module configuration records
301     unset_all_config_for_plugin($pluginname);
303     // delete message provider
304     message_provider_uninstall($component);
306     // delete message processor
307     if ($type === 'message') {
308         message_processor_uninstall($name);
309     }
311     // delete the plugin tables
312     $xmldbfilepath = $plugindirectory . '/db/install.xml';
313     drop_plugin_tables($component, $xmldbfilepath, false);
314     if ($type === 'mod' or $type === 'block') {
315         // non-frankenstyle table prefixes
316         drop_plugin_tables($name, $xmldbfilepath, false);
317     }
319     // delete the capabilities that were defined by this module
320     capabilities_cleanup($component);
322     // remove event handlers and dequeue pending events
323     events_uninstall($component);
325     // Delete all remaining files in the filepool owned by the component.
326     $fs = get_file_storage();
327     $fs->delete_component_files($component);
329     // Finally purge all caches.
330     purge_all_caches();
332     echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
335 /**
336  * Returns the version of installed component
337  *
338  * @param string $component component name
339  * @param string $source either 'disk' or 'installed' - where to get the version information from
340  * @return string|bool version number or false if the component is not found
341  */
342 function get_component_version($component, $source='installed') {
343     global $CFG, $DB;
345     list($type, $name) = core_component::normalize_component($component);
347     // moodle core or a core subsystem
348     if ($type === 'core') {
349         if ($source === 'installed') {
350             if (empty($CFG->version)) {
351                 return false;
352             } else {
353                 return $CFG->version;
354             }
355         } else {
356             if (!is_readable($CFG->dirroot.'/version.php')) {
357                 return false;
358             } else {
359                 $version = null; //initialize variable for IDEs
360                 include($CFG->dirroot.'/version.php');
361                 return $version;
362             }
363         }
364     }
366     // activity module
367     if ($type === 'mod') {
368         if ($source === 'installed') {
369             return $DB->get_field('modules', 'version', array('name'=>$name));
370         } else {
371             $mods = core_component::get_plugin_list('mod');
372             if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
373                 return false;
374             } else {
375                 $module = new stdclass();
376                 include($mods[$name].'/version.php');
377                 return $module->version;
378             }
379         }
380     }
382     // block
383     if ($type === 'block') {
384         if ($source === 'installed') {
385             return $DB->get_field('block', 'version', array('name'=>$name));
386         } else {
387             $blocks = core_component::get_plugin_list('block');
388             if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
389                 return false;
390             } else {
391                 $plugin = new stdclass();
392                 include($blocks[$name].'/version.php');
393                 return $plugin->version;
394             }
395         }
396     }
398     // all other plugin types
399     if ($source === 'installed') {
400         return get_config($type.'_'.$name, 'version');
401     } else {
402         $plugins = core_component::get_plugin_list($type);
403         if (empty($plugins[$name])) {
404             return false;
405         } else {
406             $plugin = new stdclass();
407             include($plugins[$name].'/version.php');
408             return $plugin->version;
409         }
410     }
413 /**
414  * Delete all plugin tables
415  *
416  * @param string $name Name of plugin, used as table prefix
417  * @param string $file Path to install.xml file
418  * @param bool $feedback defaults to true
419  * @return bool Always returns true
420  */
421 function drop_plugin_tables($name, $file, $feedback=true) {
422     global $CFG, $DB;
424     // first try normal delete
425     if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
426         return true;
427     }
429     // then try to find all tables that start with name and are not in any xml file
430     $used_tables = get_used_table_names();
432     $tables = $DB->get_tables();
434     /// Iterate over, fixing id fields as necessary
435     foreach ($tables as $table) {
436         if (in_array($table, $used_tables)) {
437             continue;
438         }
440         if (strpos($table, $name) !== 0) {
441             continue;
442         }
444         // found orphan table --> delete it
445         if ($DB->get_manager()->table_exists($table)) {
446             $xmldb_table = new xmldb_table($table);
447             $DB->get_manager()->drop_table($xmldb_table);
448         }
449     }
451     return true;
454 /**
455  * Returns names of all known tables == tables that moodle knows about.
456  *
457  * @return array Array of lowercase table names
458  */
459 function get_used_table_names() {
460     $table_names = array();
461     $dbdirs = get_db_directories();
463     foreach ($dbdirs as $dbdir) {
464         $file = $dbdir.'/install.xml';
466         $xmldb_file = new xmldb_file($file);
468         if (!$xmldb_file->fileExists()) {
469             continue;
470         }
472         $loaded    = $xmldb_file->loadXMLStructure();
473         $structure = $xmldb_file->getStructure();
475         if ($loaded and $tables = $structure->getTables()) {
476             foreach($tables as $table) {
477                 $table_names[] = strtolower($table->getName());
478             }
479         }
480     }
482     return $table_names;
485 /**
486  * Returns list of all directories where we expect install.xml files
487  * @return array Array of paths
488  */
489 function get_db_directories() {
490     global $CFG;
492     $dbdirs = array();
494     /// First, the main one (lib/db)
495     $dbdirs[] = $CFG->libdir.'/db';
497     /// Then, all the ones defined by core_component::get_plugin_types()
498     $plugintypes = core_component::get_plugin_types();
499     foreach ($plugintypes as $plugintype => $pluginbasedir) {
500         if ($plugins = core_component::get_plugin_list($plugintype)) {
501             foreach ($plugins as $plugin => $plugindir) {
502                 $dbdirs[] = $plugindir.'/db';
503             }
504         }
505     }
507     return $dbdirs;
510 /**
511  * Try to obtain or release the cron lock.
512  * @param string  $name  name of lock
513  * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionally
514  * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
515  * @return bool true if lock obtained
516  */
517 function set_cron_lock($name, $until, $ignorecurrent=false) {
518     global $DB;
519     if (empty($name)) {
520         debugging("Tried to get a cron lock for a null fieldname");
521         return false;
522     }
524     // remove lock by force == remove from config table
525     if (is_null($until)) {
526         set_config($name, null);
527         return true;
528     }
530     if (!$ignorecurrent) {
531         // read value from db - other processes might have changed it
532         $value = $DB->get_field('config', 'value', array('name'=>$name));
534         if ($value and $value > time()) {
535             //lock active
536             return false;
537         }
538     }
540     set_config($name, $until);
541     return true;
544 /**
545  * Test if and critical warnings are present
546  * @return bool
547  */
548 function admin_critical_warnings_present() {
549     global $SESSION;
551     if (!has_capability('moodle/site:config', context_system::instance())) {
552         return 0;
553     }
555     if (!isset($SESSION->admin_critical_warning)) {
556         $SESSION->admin_critical_warning = 0;
557         if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
558             $SESSION->admin_critical_warning = 1;
559         }
560     }
562     return $SESSION->admin_critical_warning;
565 /**
566  * Detects if float supports at least 10 decimal digits
567  *
568  * Detects if float supports at least 10 decimal digits
569  * and also if float-->string conversion works as expected.
570  *
571  * @return bool true if problem found
572  */
573 function is_float_problem() {
574     $num1 = 2009010200.01;
575     $num2 = 2009010200.02;
577     return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
580 /**
581  * Try to verify that dataroot is not accessible from web.
582  *
583  * Try to verify that dataroot is not accessible from web.
584  * It is not 100% correct but might help to reduce number of vulnerable sites.
585  * Protection from httpd.conf and .htaccess is not detected properly.
586  *
587  * @uses INSECURE_DATAROOT_WARNING
588  * @uses INSECURE_DATAROOT_ERROR
589  * @param bool $fetchtest try to test public access by fetching file, default false
590  * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
591  */
592 function is_dataroot_insecure($fetchtest=false) {
593     global $CFG;
595     $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
597     $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
598     $rp = strrev(trim($rp, '/'));
599     $rp = explode('/', $rp);
600     foreach($rp as $r) {
601         if (strpos($siteroot, '/'.$r.'/') === 0) {
602             $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
603         } else {
604             break; // probably alias root
605         }
606     }
608     $siteroot = strrev($siteroot);
609     $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
611     if (strpos($dataroot, $siteroot) !== 0) {
612         return false;
613     }
615     if (!$fetchtest) {
616         return INSECURE_DATAROOT_WARNING;
617     }
619     // now try all methods to fetch a test file using http protocol
621     $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
622     preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
623     $httpdocroot = $matches[1];
624     $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
625     make_upload_directory('diag');
626     $testfile = $CFG->dataroot.'/diag/public.txt';
627     if (!file_exists($testfile)) {
628         file_put_contents($testfile, 'test file, do not delete');
629         @chmod($testfile, $CFG->filepermissions);
630     }
631     $teststr = trim(file_get_contents($testfile));
632     if (empty($teststr)) {
633     // hmm, strange
634         return INSECURE_DATAROOT_WARNING;
635     }
637     $testurl = $datarooturl.'/diag/public.txt';
638     if (extension_loaded('curl') and
639         !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
640         !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
641         ($ch = @curl_init($testurl)) !== false) {
642         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
643         curl_setopt($ch, CURLOPT_HEADER, false);
644         $data = curl_exec($ch);
645         if (!curl_errno($ch)) {
646             $data = trim($data);
647             if ($data === $teststr) {
648                 curl_close($ch);
649                 return INSECURE_DATAROOT_ERROR;
650             }
651         }
652         curl_close($ch);
653     }
655     if ($data = @file_get_contents($testurl)) {
656         $data = trim($data);
657         if ($data === $teststr) {
658             return INSECURE_DATAROOT_ERROR;
659         }
660     }
662     preg_match('|https?://([^/]+)|i', $testurl, $matches);
663     $sitename = $matches[1];
664     $error = 0;
665     if ($fp = @fsockopen($sitename, 80, $error)) {
666         preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
667         $localurl = $matches[1];
668         $out = "GET $localurl HTTP/1.1\r\n";
669         $out .= "Host: $sitename\r\n";
670         $out .= "Connection: Close\r\n\r\n";
671         fwrite($fp, $out);
672         $data = '';
673         $incoming = false;
674         while (!feof($fp)) {
675             if ($incoming) {
676                 $data .= fgets($fp, 1024);
677             } else if (@fgets($fp, 1024) === "\r\n") {
678                     $incoming = true;
679                 }
680         }
681         fclose($fp);
682         $data = trim($data);
683         if ($data === $teststr) {
684             return INSECURE_DATAROOT_ERROR;
685         }
686     }
688     return INSECURE_DATAROOT_WARNING;
691 /**
692  * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
693  */
694 function enable_cli_maintenance_mode() {
695     global $CFG;
697     if (file_exists("$CFG->dataroot/climaintenance.html")) {
698         unlink("$CFG->dataroot/climaintenance.html");
699     }
701     if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
702         $data = $CFG->maintenance_message;
703         $data = bootstrap_renderer::early_error_content($data, null, null, null);
704         $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
706     } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
707         $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
709     } else {
710         $data = get_string('sitemaintenance', 'admin');
711         $data = bootstrap_renderer::early_error_content($data, null, null, null);
712         $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
713     }
715     file_put_contents("$CFG->dataroot/climaintenance.html", $data);
716     chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
719 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
722 /**
723  * Interface for anything appearing in the admin tree
724  *
725  * The interface that is implemented by anything that appears in the admin tree
726  * block. It forces inheriting classes to define a method for checking user permissions
727  * and methods for finding something in the admin tree.
728  *
729  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
730  */
731 interface part_of_admin_tree {
733 /**
734  * Finds a named part_of_admin_tree.
735  *
736  * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
737  * and not parentable_part_of_admin_tree, then this function should only check if
738  * $this->name matches $name. If it does, it should return a reference to $this,
739  * otherwise, it should return a reference to NULL.
740  *
741  * If a class inherits parentable_part_of_admin_tree, this method should be called
742  * recursively on all child objects (assuming, of course, the parent object's name
743  * doesn't match the search criterion).
744  *
745  * @param string $name The internal name of the part_of_admin_tree we're searching for.
746  * @return mixed An object reference or a NULL reference.
747  */
748     public function locate($name);
750     /**
751      * Removes named part_of_admin_tree.
752      *
753      * @param string $name The internal name of the part_of_admin_tree we want to remove.
754      * @return bool success.
755      */
756     public function prune($name);
758     /**
759      * Search using query
760      * @param string $query
761      * @return mixed array-object structure of found settings and pages
762      */
763     public function search($query);
765     /**
766      * Verifies current user's access to this part_of_admin_tree.
767      *
768      * Used to check if the current user has access to this part of the admin tree or
769      * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
770      * then this method is usually just a call to has_capability() in the site context.
771      *
772      * If a class inherits parentable_part_of_admin_tree, this method should return the
773      * logical OR of the return of check_access() on all child objects.
774      *
775      * @return bool True if the user has access, false if she doesn't.
776      */
777     public function check_access();
779     /**
780      * Mostly useful for removing of some parts of the tree in admin tree block.
781      *
782      * @return True is hidden from normal list view
783      */
784     public function is_hidden();
786     /**
787      * Show we display Save button at the page bottom?
788      * @return bool
789      */
790     public function show_save();
794 /**
795  * Interface implemented by any part_of_admin_tree that has children.
796  *
797  * The interface implemented by any part_of_admin_tree that can be a parent
798  * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
799  * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
800  * include an add method for adding other part_of_admin_tree objects as children.
801  *
802  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
803  */
804 interface parentable_part_of_admin_tree extends part_of_admin_tree {
806 /**
807  * Adds a part_of_admin_tree object to the admin tree.
808  *
809  * Used to add a part_of_admin_tree object to this object or a child of this
810  * object. $something should only be added if $destinationname matches
811  * $this->name. If it doesn't, add should be called on child objects that are
812  * also parentable_part_of_admin_tree's.
813  *
814  * $something should be appended as the last child in the $destinationname. If the
815  * $beforesibling is specified, $something should be prepended to it. If the given
816  * sibling is not found, $something should be appended to the end of $destinationname
817  * and a developer debugging message should be displayed.
818  *
819  * @param string $destinationname The internal name of the new parent for $something.
820  * @param part_of_admin_tree $something The object to be added.
821  * @return bool True on success, false on failure.
822  */
823     public function add($destinationname, $something, $beforesibling = null);
828 /**
829  * The object used to represent folders (a.k.a. categories) in the admin tree block.
830  *
831  * Each admin_category object contains a number of part_of_admin_tree objects.
832  *
833  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
834  */
835 class admin_category implements parentable_part_of_admin_tree {
837     /** @var mixed An array of part_of_admin_tree objects that are this object's children */
838     public $children;
839     /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
840     public $name;
841     /** @var string The displayed name for this category. Usually obtained through get_string() */
842     public $visiblename;
843     /** @var bool Should this category be hidden in admin tree block? */
844     public $hidden;
845     /** @var mixed Either a string or an array or strings */
846     public $path;
847     /** @var mixed Either a string or an array or strings */
848     public $visiblepath;
850     /** @var array fast lookup category cache, all categories of one tree point to one cache */
851     protected $category_cache;
853     /**
854      * Constructor for an empty admin category
855      *
856      * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
857      * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
858      * @param bool $hidden hide category in admin tree block, defaults to false
859      */
860     public function __construct($name, $visiblename, $hidden=false) {
861         $this->children    = array();
862         $this->name        = $name;
863         $this->visiblename = $visiblename;
864         $this->hidden      = $hidden;
865     }
867     /**
868      * Returns a reference to the part_of_admin_tree object with internal name $name.
869      *
870      * @param string $name The internal name of the object we want.
871      * @param bool $findpath initialize path and visiblepath arrays
872      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
873      *                  defaults to false
874      */
875     public function locate($name, $findpath=false) {
876         if (!isset($this->category_cache[$this->name])) {
877             // somebody much have purged the cache
878             $this->category_cache[$this->name] = $this;
879         }
881         if ($this->name == $name) {
882             if ($findpath) {
883                 $this->visiblepath[] = $this->visiblename;
884                 $this->path[]        = $this->name;
885             }
886             return $this;
887         }
889         // quick category lookup
890         if (!$findpath and isset($this->category_cache[$name])) {
891             return $this->category_cache[$name];
892         }
894         $return = NULL;
895         foreach($this->children as $childid=>$unused) {
896             if ($return = $this->children[$childid]->locate($name, $findpath)) {
897                 break;
898             }
899         }
901         if (!is_null($return) and $findpath) {
902             $return->visiblepath[] = $this->visiblename;
903             $return->path[]        = $this->name;
904         }
906         return $return;
907     }
909     /**
910      * Search using query
911      *
912      * @param string query
913      * @return mixed array-object structure of found settings and pages
914      */
915     public function search($query) {
916         $result = array();
917         foreach ($this->children as $child) {
918             $subsearch = $child->search($query);
919             if (!is_array($subsearch)) {
920                 debugging('Incorrect search result from '.$child->name);
921                 continue;
922             }
923             $result = array_merge($result, $subsearch);
924         }
925         return $result;
926     }
928     /**
929      * Removes part_of_admin_tree object with internal name $name.
930      *
931      * @param string $name The internal name of the object we want to remove.
932      * @return bool success
933      */
934     public function prune($name) {
936         if ($this->name == $name) {
937             return false;  //can not remove itself
938         }
940         foreach($this->children as $precedence => $child) {
941             if ($child->name == $name) {
942                 // clear cache and delete self
943                 while($this->category_cache) {
944                     // delete the cache, but keep the original array address
945                     array_pop($this->category_cache);
946                 }
947                 unset($this->children[$precedence]);
948                 return true;
949             } else if ($this->children[$precedence]->prune($name)) {
950                 return true;
951             }
952         }
953         return false;
954     }
956     /**
957      * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
958      *
959      * By default the new part of the tree is appended as the last child of the parent. You
960      * can specify a sibling node that the new part should be prepended to. If the given
961      * sibling is not found, the part is appended to the end (as it would be by default) and
962      * a developer debugging message is displayed.
963      *
964      * @throws coding_exception if the $beforesibling is empty string or is not string at all.
965      * @param string $destinationame The internal name of the immediate parent that we want for $something.
966      * @param mixed $something A part_of_admin_tree or setting instance to be added.
967      * @param string $beforesibling The name of the parent's child the $something should be prepended to.
968      * @return bool True if successfully added, false if $something can not be added.
969      */
970     public function add($parentname, $something, $beforesibling = null) {
971         global $CFG;
973         $parent = $this->locate($parentname);
974         if (is_null($parent)) {
975             debugging('parent does not exist!');
976             return false;
977         }
979         if ($something instanceof part_of_admin_tree) {
980             if (!($parent instanceof parentable_part_of_admin_tree)) {
981                 debugging('error - parts of tree can be inserted only into parentable parts');
982                 return false;
983             }
984             if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
985                 // The name of the node is already used, simply warn the developer that this should not happen.
986                 // It is intentional to check for the debug level before performing the check.
987                 debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
988             }
989             if (is_null($beforesibling)) {
990                 // Append $something as the parent's last child.
991                 $parent->children[] = $something;
992             } else {
993                 if (!is_string($beforesibling) or trim($beforesibling) === '') {
994                     throw new coding_exception('Unexpected value of the beforesibling parameter');
995                 }
996                 // Try to find the position of the sibling.
997                 $siblingposition = null;
998                 foreach ($parent->children as $childposition => $child) {
999                     if ($child->name === $beforesibling) {
1000                         $siblingposition = $childposition;
1001                         break;
1002                     }
1003                 }
1004                 if (is_null($siblingposition)) {
1005                     debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
1006                     $parent->children[] = $something;
1007                 } else {
1008                     $parent->children = array_merge(
1009                         array_slice($parent->children, 0, $siblingposition),
1010                         array($something),
1011                         array_slice($parent->children, $siblingposition)
1012                     );
1013                 }
1014             }
1015             if ($something instanceof admin_category) {
1016                 if (isset($this->category_cache[$something->name])) {
1017                     debugging('Duplicate admin category name: '.$something->name);
1018                 } else {
1019                     $this->category_cache[$something->name] = $something;
1020                     $something->category_cache =& $this->category_cache;
1021                     foreach ($something->children as $child) {
1022                         // just in case somebody already added subcategories
1023                         if ($child instanceof admin_category) {
1024                             if (isset($this->category_cache[$child->name])) {
1025                                 debugging('Duplicate admin category name: '.$child->name);
1026                             } else {
1027                                 $this->category_cache[$child->name] = $child;
1028                                 $child->category_cache =& $this->category_cache;
1029                             }
1030                         }
1031                     }
1032                 }
1033             }
1034             return true;
1036         } else {
1037             debugging('error - can not add this element');
1038             return false;
1039         }
1041     }
1043     /**
1044      * Checks if the user has access to anything in this category.
1045      *
1046      * @return bool True if the user has access to at least one child in this category, false otherwise.
1047      */
1048     public function check_access() {
1049         foreach ($this->children as $child) {
1050             if ($child->check_access()) {
1051                 return true;
1052             }
1053         }
1054         return false;
1055     }
1057     /**
1058      * Is this category hidden in admin tree block?
1059      *
1060      * @return bool True if hidden
1061      */
1062     public function is_hidden() {
1063         return $this->hidden;
1064     }
1066     /**
1067      * Show we display Save button at the page bottom?
1068      * @return bool
1069      */
1070     public function show_save() {
1071         foreach ($this->children as $child) {
1072             if ($child->show_save()) {
1073                 return true;
1074             }
1075         }
1076         return false;
1077     }
1081 /**
1082  * Root of admin settings tree, does not have any parent.
1083  *
1084  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1085  */
1086 class admin_root extends admin_category {
1087 /** @var array List of errors */
1088     public $errors;
1089     /** @var string search query */
1090     public $search;
1091     /** @var bool full tree flag - true means all settings required, false only pages required */
1092     public $fulltree;
1093     /** @var bool flag indicating loaded tree */
1094     public $loaded;
1095     /** @var mixed site custom defaults overriding defaults in settings files*/
1096     public $custom_defaults;
1098     /**
1099      * @param bool $fulltree true means all settings required,
1100      *                            false only pages required
1101      */
1102     public function __construct($fulltree) {
1103         global $CFG;
1105         parent::__construct('root', get_string('administration'), false);
1106         $this->errors   = array();
1107         $this->search   = '';
1108         $this->fulltree = $fulltree;
1109         $this->loaded   = false;
1111         $this->category_cache = array();
1113         // load custom defaults if found
1114         $this->custom_defaults = null;
1115         $defaultsfile = "$CFG->dirroot/local/defaults.php";
1116         if (is_readable($defaultsfile)) {
1117             $defaults = array();
1118             include($defaultsfile);
1119             if (is_array($defaults) and count($defaults)) {
1120                 $this->custom_defaults = $defaults;
1121             }
1122         }
1123     }
1125     /**
1126      * Empties children array, and sets loaded to false
1127      *
1128      * @param bool $requirefulltree
1129      */
1130     public function purge_children($requirefulltree) {
1131         $this->children = array();
1132         $this->fulltree = ($requirefulltree || $this->fulltree);
1133         $this->loaded   = false;
1134         //break circular dependencies - this helps PHP 5.2
1135         while($this->category_cache) {
1136             array_pop($this->category_cache);
1137         }
1138         $this->category_cache = array();
1139     }
1143 /**
1144  * Links external PHP pages into the admin tree.
1145  *
1146  * See detailed usage example at the top of this document (adminlib.php)
1147  *
1148  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1149  */
1150 class admin_externalpage implements part_of_admin_tree {
1152     /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1153     public $name;
1155     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1156     public $visiblename;
1158     /** @var string The external URL that we should link to when someone requests this external page. */
1159     public $url;
1161     /** @var string The role capability/permission a user must have to access this external page. */
1162     public $req_capability;
1164     /** @var object The context in which capability/permission should be checked, default is site context. */
1165     public $context;
1167     /** @var bool hidden in admin tree block. */
1168     public $hidden;
1170     /** @var mixed either string or array of string */
1171     public $path;
1173     /** @var array list of visible names of page parents */
1174     public $visiblepath;
1176     /**
1177      * Constructor for adding an external page into the admin tree.
1178      *
1179      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1180      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1181      * @param string $url The external URL that we should link to when someone requests this external page.
1182      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1183      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1184      * @param stdClass $context The context the page relates to. Not sure what happens
1185      *      if you specify something other than system or front page. Defaults to system.
1186      */
1187     public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1188         $this->name        = $name;
1189         $this->visiblename = $visiblename;
1190         $this->url         = $url;
1191         if (is_array($req_capability)) {
1192             $this->req_capability = $req_capability;
1193         } else {
1194             $this->req_capability = array($req_capability);
1195         }
1196         $this->hidden = $hidden;
1197         $this->context = $context;
1198     }
1200     /**
1201      * Returns a reference to the part_of_admin_tree object with internal name $name.
1202      *
1203      * @param string $name The internal name of the object we want.
1204      * @param bool $findpath defaults to false
1205      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1206      */
1207     public function locate($name, $findpath=false) {
1208         if ($this->name == $name) {
1209             if ($findpath) {
1210                 $this->visiblepath = array($this->visiblename);
1211                 $this->path        = array($this->name);
1212             }
1213             return $this;
1214         } else {
1215             $return = NULL;
1216             return $return;
1217         }
1218     }
1220     /**
1221      * This function always returns false, required function by interface
1222      *
1223      * @param string $name
1224      * @return false
1225      */
1226     public function prune($name) {
1227         return false;
1228     }
1230     /**
1231      * Search using query
1232      *
1233      * @param string $query
1234      * @return mixed array-object structure of found settings and pages
1235      */
1236     public function search($query) {
1237         $found = false;
1238         if (strpos(strtolower($this->name), $query) !== false) {
1239             $found = true;
1240         } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1241                 $found = true;
1242             }
1243         if ($found) {
1244             $result = new stdClass();
1245             $result->page     = $this;
1246             $result->settings = array();
1247             return array($this->name => $result);
1248         } else {
1249             return array();
1250         }
1251     }
1253     /**
1254      * Determines if the current user has access to this external page based on $this->req_capability.
1255      *
1256      * @return bool True if user has access, false otherwise.
1257      */
1258     public function check_access() {
1259         global $CFG;
1260         $context = empty($this->context) ? context_system::instance() : $this->context;
1261         foreach($this->req_capability as $cap) {
1262             if (has_capability($cap, $context)) {
1263                 return true;
1264             }
1265         }
1266         return false;
1267     }
1269     /**
1270      * Is this external page hidden in admin tree block?
1271      *
1272      * @return bool True if hidden
1273      */
1274     public function is_hidden() {
1275         return $this->hidden;
1276     }
1278     /**
1279      * Show we display Save button at the page bottom?
1280      * @return bool
1281      */
1282     public function show_save() {
1283         return false;
1284     }
1288 /**
1289  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1290  *
1291  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1292  */
1293 class admin_settingpage implements part_of_admin_tree {
1295     /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1296     public $name;
1298     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1299     public $visiblename;
1301     /** @var mixed An array of admin_setting objects that are part of this setting page. */
1302     public $settings;
1304     /** @var string The role capability/permission a user must have to access this external page. */
1305     public $req_capability;
1307     /** @var object The context in which capability/permission should be checked, default is site context. */
1308     public $context;
1310     /** @var bool hidden in admin tree block. */
1311     public $hidden;
1313     /** @var mixed string of paths or array of strings of paths */
1314     public $path;
1316     /** @var array list of visible names of page parents */
1317     public $visiblepath;
1319     /**
1320      * see admin_settingpage for details of this function
1321      *
1322      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1323      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1324      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1325      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1326      * @param stdClass $context The context the page relates to. Not sure what happens
1327      *      if you specify something other than system or front page. Defaults to system.
1328      */
1329     public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1330         $this->settings    = new stdClass();
1331         $this->name        = $name;
1332         $this->visiblename = $visiblename;
1333         if (is_array($req_capability)) {
1334             $this->req_capability = $req_capability;
1335         } else {
1336             $this->req_capability = array($req_capability);
1337         }
1338         $this->hidden      = $hidden;
1339         $this->context     = $context;
1340     }
1342     /**
1343      * see admin_category
1344      *
1345      * @param string $name
1346      * @param bool $findpath
1347      * @return mixed Object (this) if name ==  this->name, else returns null
1348      */
1349     public function locate($name, $findpath=false) {
1350         if ($this->name == $name) {
1351             if ($findpath) {
1352                 $this->visiblepath = array($this->visiblename);
1353                 $this->path        = array($this->name);
1354             }
1355             return $this;
1356         } else {
1357             $return = NULL;
1358             return $return;
1359         }
1360     }
1362     /**
1363      * Search string in settings page.
1364      *
1365      * @param string $query
1366      * @return array
1367      */
1368     public function search($query) {
1369         $found = array();
1371         foreach ($this->settings as $setting) {
1372             if ($setting->is_related($query)) {
1373                 $found[] = $setting;
1374             }
1375         }
1377         if ($found) {
1378             $result = new stdClass();
1379             $result->page     = $this;
1380             $result->settings = $found;
1381             return array($this->name => $result);
1382         }
1384         $found = false;
1385         if (strpos(strtolower($this->name), $query) !== false) {
1386             $found = true;
1387         } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1388                 $found = true;
1389             }
1390         if ($found) {
1391             $result = new stdClass();
1392             $result->page     = $this;
1393             $result->settings = array();
1394             return array($this->name => $result);
1395         } else {
1396             return array();
1397         }
1398     }
1400     /**
1401      * This function always returns false, required by interface
1402      *
1403      * @param string $name
1404      * @return bool Always false
1405      */
1406     public function prune($name) {
1407         return false;
1408     }
1410     /**
1411      * adds an admin_setting to this admin_settingpage
1412      *
1413      * 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
1414      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1415      *
1416      * @param object $setting is the admin_setting object you want to add
1417      * @return bool true if successful, false if not
1418      */
1419     public function add($setting) {
1420         if (!($setting instanceof admin_setting)) {
1421             debugging('error - not a setting instance');
1422             return false;
1423         }
1425         $this->settings->{$setting->name} = $setting;
1426         return true;
1427     }
1429     /**
1430      * see admin_externalpage
1431      *
1432      * @return bool Returns true for yes false for no
1433      */
1434     public function check_access() {
1435         global $CFG;
1436         $context = empty($this->context) ? context_system::instance() : $this->context;
1437         foreach($this->req_capability as $cap) {
1438             if (has_capability($cap, $context)) {
1439                 return true;
1440             }
1441         }
1442         return false;
1443     }
1445     /**
1446      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1447      * @return string Returns an XHTML string
1448      */
1449     public function output_html() {
1450         $adminroot = admin_get_root();
1451         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1452         foreach($this->settings as $setting) {
1453             $fullname = $setting->get_full_name();
1454             if (array_key_exists($fullname, $adminroot->errors)) {
1455                 $data = $adminroot->errors[$fullname]->data;
1456             } else {
1457                 $data = $setting->get_setting();
1458                 // do not use defaults if settings not available - upgrade settings handles the defaults!
1459             }
1460             $return .= $setting->output_html($data);
1461         }
1462         $return .= '</fieldset>';
1463         return $return;
1464     }
1466     /**
1467      * Is this settings page hidden in admin tree block?
1468      *
1469      * @return bool True if hidden
1470      */
1471     public function is_hidden() {
1472         return $this->hidden;
1473     }
1475     /**
1476      * Show we display Save button at the page bottom?
1477      * @return bool
1478      */
1479     public function show_save() {
1480         foreach($this->settings as $setting) {
1481             if (empty($setting->nosave)) {
1482                 return true;
1483             }
1484         }
1485         return false;
1486     }
1490 /**
1491  * Admin settings class. Only exists on setting pages.
1492  * Read & write happens at this level; no authentication.
1493  *
1494  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1495  */
1496 abstract class admin_setting {
1497     /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1498     public $name;
1499     /** @var string localised name */
1500     public $visiblename;
1501     /** @var string localised long description in Markdown format */
1502     public $description;
1503     /** @var mixed Can be string or array of string */
1504     public $defaultsetting;
1505     /** @var string */
1506     public $updatedcallback;
1507     /** @var mixed can be String or Null.  Null means main config table */
1508     public $plugin; // null means main config table
1509     /** @var bool true indicates this setting does not actually save anything, just information */
1510     public $nosave = false;
1511     /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1512     public $affectsmodinfo = false;
1513     /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */
1514     private $flags = array();
1516     /**
1517      * Constructor
1518      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1519      *                     or 'myplugin/mysetting' for ones in config_plugins.
1520      * @param string $visiblename localised name
1521      * @param string $description localised long description
1522      * @param mixed $defaultsetting string or array depending on implementation
1523      */
1524     public function __construct($name, $visiblename, $description, $defaultsetting) {
1525         $this->parse_setting_name($name);
1526         $this->visiblename    = $visiblename;
1527         $this->description    = $description;
1528         $this->defaultsetting = $defaultsetting;
1529     }
1531     /**
1532      * Generic function to add a flag to this admin setting.
1533      *
1534      * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1535      * @param bool $default - The default for the flag
1536      * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name.
1537      * @param string $displayname - The display name for this flag. Used as a label next to the checkbox.
1538      */
1539     protected function set_flag_options($enabled, $default, $shortname, $displayname) {
1540         if (empty($this->flags[$shortname])) {
1541             $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname);
1542         } else {
1543             $this->flags[$shortname]->set_options($enabled, $default);
1544         }
1545     }
1547     /**
1548      * Set the enabled options flag on this admin setting.
1549      *
1550      * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1551      * @param bool $default - The default for the flag
1552      */
1553     public function set_enabled_flag_options($enabled, $default) {
1554         $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin'));
1555     }
1557     /**
1558      * Set the advanced options flag on this admin setting.
1559      *
1560      * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1561      * @param bool $default - The default for the flag
1562      */
1563     public function set_advanced_flag_options($enabled, $default) {
1564         $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced'));
1565     }
1568     /**
1569      * Set the locked options flag on this admin setting.
1570      *
1571      * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED
1572      * @param bool $default - The default for the flag
1573      */
1574     public function set_locked_flag_options($enabled, $default) {
1575         $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
1576     }
1578     /**
1579      * Get the currently saved value for a setting flag
1580      *
1581      * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting.
1582      * @return bool
1583      */
1584     public function get_setting_flag_value(admin_setting_flag $flag) {
1585         $value = $this->config_read($this->name . '_' . $flag->get_shortname());
1586         if (!isset($value)) {
1587             $value = $flag->get_default();
1588         }
1590         return !empty($value);
1591     }
1593     /**
1594      * Get the list of defaults for the flags on this setting.
1595      *
1596      * @param array of strings describing the defaults for this setting. This is appended to by this function.
1597      */
1598     public function get_setting_flag_defaults(& $defaults) {
1599         foreach ($this->flags as $flag) {
1600             if ($flag->is_enabled() && $flag->get_default()) {
1601                 $defaults[] = $flag->get_displayname();
1602             }
1603         }
1604     }
1606     /**
1607      * Output the input fields for the advanced and locked flags on this setting.
1608      *
1609      * @param bool $adv - The current value of the advanced flag.
1610      * @param bool $locked - The current value of the locked flag.
1611      * @return string $output - The html for the flags.
1612      */
1613     public function output_setting_flags() {
1614         $output = '';
1616         foreach ($this->flags as $flag) {
1617             if ($flag->is_enabled()) {
1618                 $output .= $flag->output_setting_flag($this);
1619             }
1620         }
1622         if (!empty($output)) {
1623             return html_writer::tag('span', $output, array('class' => 'adminsettingsflags'));
1624         }
1625         return $output;
1626     }
1628     /**
1629      * Write the values of the flags for this admin setting.
1630      *
1631      * @param array $data - The data submitted from the form or null to set the default value for new installs.
1632      * @return bool - true if successful.
1633      */
1634     public function write_setting_flags($data) {
1635         $result = true;
1636         foreach ($this->flags as $flag) {
1637             $result = $result && $flag->write_setting_flag($this, $data);
1638         }
1639         return $result;
1640     }
1642     /**
1643      * Set up $this->name and potentially $this->plugin
1644      *
1645      * Set up $this->name and possibly $this->plugin based on whether $name looks
1646      * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1647      * on the names, that is, output a developer debug warning if the name
1648      * contains anything other than [a-zA-Z0-9_]+.
1649      *
1650      * @param string $name the setting name passed in to the constructor.
1651      */
1652     private function parse_setting_name($name) {
1653         $bits = explode('/', $name);
1654         if (count($bits) > 2) {
1655             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1656         }
1657         $this->name = array_pop($bits);
1658         if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1659             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1660         }
1661         if (!empty($bits)) {
1662             $this->plugin = array_pop($bits);
1663             if ($this->plugin === 'moodle') {
1664                 $this->plugin = null;
1665             } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1666                     throw new moodle_exception('invalidadminsettingname', '', '', $name);
1667                 }
1668         }
1669     }
1671     /**
1672      * Returns the fullname prefixed by the plugin
1673      * @return string
1674      */
1675     public function get_full_name() {
1676         return 's_'.$this->plugin.'_'.$this->name;
1677     }
1679     /**
1680      * Returns the ID string based on plugin and name
1681      * @return string
1682      */
1683     public function get_id() {
1684         return 'id_s_'.$this->plugin.'_'.$this->name;
1685     }
1687     /**
1688      * @param bool $affectsmodinfo If true, changes to this setting will
1689      *   cause the course cache to be rebuilt
1690      */
1691     public function set_affects_modinfo($affectsmodinfo) {
1692         $this->affectsmodinfo = $affectsmodinfo;
1693     }
1695     /**
1696      * Returns the config if possible
1697      *
1698      * @return mixed returns config if successful else null
1699      */
1700     public function config_read($name) {
1701         global $CFG;
1702         if (!empty($this->plugin)) {
1703             $value = get_config($this->plugin, $name);
1704             return $value === false ? NULL : $value;
1706         } else {
1707             if (isset($CFG->$name)) {
1708                 return $CFG->$name;
1709             } else {
1710                 return NULL;
1711             }
1712         }
1713     }
1715     /**
1716      * Used to set a config pair and log change
1717      *
1718      * @param string $name
1719      * @param mixed $value Gets converted to string if not null
1720      * @return bool Write setting to config table
1721      */
1722     public function config_write($name, $value) {
1723         global $DB, $USER, $CFG;
1725         if ($this->nosave) {
1726             return true;
1727         }
1729         // make sure it is a real change
1730         $oldvalue = get_config($this->plugin, $name);
1731         $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1732         $value = is_null($value) ? null : (string)$value;
1734         if ($oldvalue === $value) {
1735             return true;
1736         }
1738         // store change
1739         set_config($name, $value, $this->plugin);
1741         // Some admin settings affect course modinfo
1742         if ($this->affectsmodinfo) {
1743             // Clear course cache for all courses
1744             rebuild_course_cache(0, true);
1745         }
1747         add_to_config_log($name, $oldvalue, $value, $this->plugin);
1749         return true; // BC only
1750     }
1752     /**
1753      * Returns current value of this setting
1754      * @return mixed array or string depending on instance, NULL means not set yet
1755      */
1756     public abstract function get_setting();
1758     /**
1759      * Returns default setting if exists
1760      * @return mixed array or string depending on instance; NULL means no default, user must supply
1761      */
1762     public function get_defaultsetting() {
1763         $adminroot =  admin_get_root(false, false);
1764         if (!empty($adminroot->custom_defaults)) {
1765             $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1766             if (isset($adminroot->custom_defaults[$plugin])) {
1767                 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
1768                     return $adminroot->custom_defaults[$plugin][$this->name];
1769                 }
1770             }
1771         }
1772         return $this->defaultsetting;
1773     }
1775     /**
1776      * Store new setting
1777      *
1778      * @param mixed $data string or array, must not be NULL
1779      * @return string empty string if ok, string error message otherwise
1780      */
1781     public abstract function write_setting($data);
1783     /**
1784      * Return part of form with setting
1785      * This function should always be overwritten
1786      *
1787      * @param mixed $data array or string depending on setting
1788      * @param string $query
1789      * @return string
1790      */
1791     public function output_html($data, $query='') {
1792     // should be overridden
1793         return;
1794     }
1796     /**
1797      * Function called if setting updated - cleanup, cache reset, etc.
1798      * @param string $functionname Sets the function name
1799      * @return void
1800      */
1801     public function set_updatedcallback($functionname) {
1802         $this->updatedcallback = $functionname;
1803     }
1805     /**
1806      * Execute postupdatecallback if necessary.
1807      * @param mixed $original original value before write_setting()
1808      * @return bool true if changed, false if not.
1809      */
1810     public function post_write_settings($original) {
1811         // Comparison must work for arrays too.
1812         if (serialize($original) === serialize($this->get_setting())) {
1813             return false;
1814         }
1816         $callbackfunction = $this->updatedcallback;
1817         if (!empty($callbackfunction) and function_exists($callbackfunction)) {
1818             $callbackfunction($this->get_full_name());
1819         }
1820         return true;
1821     }
1823     /**
1824      * Is setting related to query text - used when searching
1825      * @param string $query
1826      * @return bool
1827      */
1828     public function is_related($query) {
1829         if (strpos(strtolower($this->name), $query) !== false) {
1830             return true;
1831         }
1832         if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
1833             return true;
1834         }
1835         if (strpos(core_text::strtolower($this->description), $query) !== false) {
1836             return true;
1837         }
1838         $current = $this->get_setting();
1839         if (!is_null($current)) {
1840             if (is_string($current)) {
1841                 if (strpos(core_text::strtolower($current), $query) !== false) {
1842                     return true;
1843                 }
1844             }
1845         }
1846         $default = $this->get_defaultsetting();
1847         if (!is_null($default)) {
1848             if (is_string($default)) {
1849                 if (strpos(core_text::strtolower($default), $query) !== false) {
1850                     return true;
1851                 }
1852             }
1853         }
1854         return false;
1855     }
1858 /**
1859  * An additional option that can be applied to an admin setting.
1860  * The currently supported options are 'ADVANCED' and 'LOCKED'.
1861  *
1862  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1863  */
1864 class admin_setting_flag {
1865     /** @var bool Flag to indicate if this option can be toggled for this setting */
1866     private $enabled = false;
1867     /** @var bool Flag to indicate if this option defaults to true or false */
1868     private $default = false;
1869     /** @var string Short string used to create setting name - e.g. 'adv' */
1870     private $shortname = '';
1871     /** @var string String used as the label for this flag */
1872     private $displayname = '';
1873     /** @const Checkbox for this flag is displayed in admin page */
1874     const ENABLED = true;
1875     /** @const Checkbox for this flag is not displayed in admin page */
1876     const DISABLED = false;
1878     /**
1879      * Constructor
1880      *
1881      * @param bool $enabled Can this option can be toggled.
1882      *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
1883      * @param bool $default The default checked state for this setting option.
1884      * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv'
1885      * @param string $displayname The displayname of this flag. Used as a label for the flag.
1886      */
1887     public function __construct($enabled, $default, $shortname, $displayname) {
1888         $this->shortname = $shortname;
1889         $this->displayname = $displayname;
1890         $this->set_options($enabled, $default);
1891     }
1893     /**
1894      * Update the values of this setting options class
1895      *
1896      * @param bool $enabled Can this option can be toggled.
1897      *                      Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED.
1898      * @param bool $default The default checked state for this setting option.
1899      */
1900     public function set_options($enabled, $default) {
1901         $this->enabled = $enabled;
1902         $this->default = $default;
1903     }
1905     /**
1906      * Should this option appear in the interface and be toggleable?
1907      *
1908      * @return bool Is it enabled?
1909      */
1910     public function is_enabled() {
1911         return $this->enabled;
1912     }
1914     /**
1915      * Should this option be checked by default?
1916      *
1917      * @return bool Is it on by default?
1918      */
1919     public function get_default() {
1920         return $this->default;
1921     }
1923     /**
1924      * Return the short name for this flag. e.g. 'adv' or 'locked'
1925      *
1926      * @return string
1927      */
1928     public function get_shortname() {
1929         return $this->shortname;
1930     }
1932     /**
1933      * Return the display name for this flag. e.g. 'Advanced' or 'Locked'
1934      *
1935      * @return string
1936      */
1937     public function get_displayname() {
1938         return $this->displayname;
1939     }
1941     /**
1942      * Save the submitted data for this flag - or set it to the default if $data is null.
1943      *
1944      * @param admin_setting $setting - The admin setting for this flag
1945      * @param array $data - The data submitted from the form or null to set the default value for new installs.
1946      * @return bool
1947      */
1948     public function write_setting_flag(admin_setting $setting, $data) {
1949         $result = true;
1950         if ($this->is_enabled()) {
1951             if (!isset($data)) {
1952                 $value = $this->get_default();
1953             } else {
1954                 $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]);
1955             }
1956             $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value);
1957         }
1959         return $result;
1961     }
1963     /**
1964      * Output the checkbox for this setting flag. Should only be called if the flag is enabled.
1965      *
1966      * @param admin_setting $setting - The admin setting for this flag
1967      * @return string - The html for the checkbox.
1968      */
1969     public function output_setting_flag(admin_setting $setting) {
1970         $value = $setting->get_setting_flag_value($this);
1971         $output = ' <input type="checkbox" class="form-checkbox" ' .
1972                         ' id="' .  $setting->get_id() . '_' . $this->get_shortname() . '" ' .
1973                         ' name="' . $setting->get_full_name() .  '_' . $this->get_shortname() . '" ' .
1974                         ' value="1" ' . ($value ? 'checked="checked"' : '') . ' />' .
1975                         ' <label for="' . $setting->get_id() . '_' . $this->get_shortname() . '">' .
1976                         $this->get_displayname() .
1977                         ' </label> ';
1978         return $output;
1979     }
1983 /**
1984  * No setting - just heading and text.
1985  *
1986  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1987  */
1988 class admin_setting_heading extends admin_setting {
1990     /**
1991      * not a setting, just text
1992      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1993      * @param string $heading heading
1994      * @param string $information text in box
1995      */
1996     public function __construct($name, $heading, $information) {
1997         $this->nosave = true;
1998         parent::__construct($name, $heading, $information, '');
1999     }
2001     /**
2002      * Always returns true
2003      * @return bool Always returns true
2004      */
2005     public function get_setting() {
2006         return true;
2007     }
2009     /**
2010      * Always returns true
2011      * @return bool Always returns true
2012      */
2013     public function get_defaultsetting() {
2014         return true;
2015     }
2017     /**
2018      * Never write settings
2019      * @return string Always returns an empty string
2020      */
2021     public function write_setting($data) {
2022     // do not write any setting
2023         return '';
2024     }
2026     /**
2027      * Returns an HTML string
2028      * @return string Returns an HTML string
2029      */
2030     public function output_html($data, $query='') {
2031         global $OUTPUT;
2032         $return = '';
2033         if ($this->visiblename != '') {
2034             $return .= $OUTPUT->heading($this->visiblename, 3, 'main');
2035         }
2036         if ($this->description != '') {
2037             $return .= $OUTPUT->box(highlight($query, markdown_to_html($this->description)), 'generalbox formsettingheading');
2038         }
2039         return $return;
2040     }
2044 /**
2045  * The most flexibly setting, user is typing text
2046  *
2047  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2048  */
2049 class admin_setting_configtext extends admin_setting {
2051     /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
2052     public $paramtype;
2053     /** @var int default field size */
2054     public $size;
2056     /**
2057      * Config text constructor
2058      *
2059      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2060      * @param string $visiblename localised
2061      * @param string $description long localised info
2062      * @param string $defaultsetting
2063      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2064      * @param int $size default field size
2065      */
2066     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2067         $this->paramtype = $paramtype;
2068         if (!is_null($size)) {
2069             $this->size  = $size;
2070         } else {
2071             $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
2072         }
2073         parent::__construct($name, $visiblename, $description, $defaultsetting);
2074     }
2076     /**
2077      * Return the setting
2078      *
2079      * @return mixed returns config if successful else null
2080      */
2081     public function get_setting() {
2082         return $this->config_read($this->name);
2083     }
2085     public function write_setting($data) {
2086         if ($this->paramtype === PARAM_INT and $data === '') {
2087         // do not complain if '' used instead of 0
2088             $data = 0;
2089         }
2090         // $data is a string
2091         $validated = $this->validate($data);
2092         if ($validated !== true) {
2093             return $validated;
2094         }
2095         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2096     }
2098     /**
2099      * Validate data before storage
2100      * @param string data
2101      * @return mixed true if ok string if error found
2102      */
2103     public function validate($data) {
2104         // allow paramtype to be a custom regex if it is the form of /pattern/
2105         if (preg_match('#^/.*/$#', $this->paramtype)) {
2106             if (preg_match($this->paramtype, $data)) {
2107                 return true;
2108             } else {
2109                 return get_string('validateerror', 'admin');
2110             }
2112         } else if ($this->paramtype === PARAM_RAW) {
2113             return true;
2115         } else {
2116             $cleaned = clean_param($data, $this->paramtype);
2117             if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
2118                 return true;
2119             } else {
2120                 return get_string('validateerror', 'admin');
2121             }
2122         }
2123     }
2125     /**
2126      * Return an XHTML string for the setting
2127      * @return string Returns an XHTML string
2128      */
2129     public function output_html($data, $query='') {
2130         $default = $this->get_defaultsetting();
2132         return format_admin_setting($this, $this->visiblename,
2133         '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
2134         $this->description, true, '', $default, $query);
2135     }
2139 /**
2140  * General text area without html editor.
2141  *
2142  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2143  */
2144 class admin_setting_configtextarea extends admin_setting_configtext {
2145     private $rows;
2146     private $cols;
2148     /**
2149      * @param string $name
2150      * @param string $visiblename
2151      * @param string $description
2152      * @param mixed $defaultsetting string or array
2153      * @param mixed $paramtype
2154      * @param string $cols The number of columns to make the editor
2155      * @param string $rows The number of rows to make the editor
2156      */
2157     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2158         $this->rows = $rows;
2159         $this->cols = $cols;
2160         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
2161     }
2163     /**
2164      * Returns an XHTML string for the editor
2165      *
2166      * @param string $data
2167      * @param string $query
2168      * @return string XHTML string for the editor
2169      */
2170     public function output_html($data, $query='') {
2171         $default = $this->get_defaultsetting();
2173         $defaultinfo = $default;
2174         if (!is_null($default) and $default !== '') {
2175             $defaultinfo = "\n".$default;
2176         }
2178         return format_admin_setting($this, $this->visiblename,
2179         '<div class="form-textarea" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'" spellcheck="true">'. s($data) .'</textarea></div>',
2180         $this->description, true, '', $defaultinfo, $query);
2181     }
2185 /**
2186  * General text area with html editor.
2187  */
2188 class admin_setting_confightmleditor extends admin_setting_configtext {
2189     private $rows;
2190     private $cols;
2192     /**
2193      * @param string $name
2194      * @param string $visiblename
2195      * @param string $description
2196      * @param mixed $defaultsetting string or array
2197      * @param mixed $paramtype
2198      */
2199     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2200         $this->rows = $rows;
2201         $this->cols = $cols;
2202         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
2203         editors_head_setup();
2204     }
2206     /**
2207      * Returns an XHTML string for the editor
2208      *
2209      * @param string $data
2210      * @param string $query
2211      * @return string XHTML string for the editor
2212      */
2213     public function output_html($data, $query='') {
2214         $default = $this->get_defaultsetting();
2216         $defaultinfo = $default;
2217         if (!is_null($default) and $default !== '') {
2218             $defaultinfo = "\n".$default;
2219         }
2221         $editor = editors_get_preferred_editor(FORMAT_HTML);
2222         $editor->use_editor($this->get_id(), array('noclean'=>true));
2224         return format_admin_setting($this, $this->visiblename,
2225         '<div class="form-textarea"><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'" spellcheck="true">'. s($data) .'</textarea></div>',
2226         $this->description, true, '', $defaultinfo, $query);
2227     }
2231 /**
2232  * Password field, allows unmasking of password
2233  *
2234  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2235  */
2236 class admin_setting_configpasswordunmask extends admin_setting_configtext {
2237     /**
2238      * Constructor
2239      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2240      * @param string $visiblename localised
2241      * @param string $description long localised info
2242      * @param string $defaultsetting default password
2243      */
2244     public function __construct($name, $visiblename, $description, $defaultsetting) {
2245         parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2246     }
2248     /**
2249      * Returns XHTML for the field
2250      * Writes Javascript into the HTML below right before the last div
2251      *
2252      * @todo Make javascript available through newer methods if possible
2253      * @param string $data Value for the field
2254      * @param string $query Passed as final argument for format_admin_setting
2255      * @return string XHTML field
2256      */
2257     public function output_html($data, $query='') {
2258         $id = $this->get_id();
2259         $unmask = get_string('unmaskpassword', 'form');
2260         $unmaskjs = '<script type="text/javascript">
2261 //<![CDATA[
2262 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
2264 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
2266 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
2268 var unmaskchb = document.createElement("input");
2269 unmaskchb.setAttribute("type", "checkbox");
2270 unmaskchb.setAttribute("id", "'.$id.'unmask");
2271 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
2272 unmaskdiv.appendChild(unmaskchb);
2274 var unmasklbl = document.createElement("label");
2275 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
2276 if (is_ie) {
2277   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
2278 } else {
2279   unmasklbl.setAttribute("for", "'.$id.'unmask");
2281 unmaskdiv.appendChild(unmasklbl);
2283 if (is_ie) {
2284   // ugly hack to work around the famous onchange IE bug
2285   unmaskchb.onclick = function() {this.blur();};
2286   unmaskdiv.onclick = function() {this.blur();};
2288 //]]>
2289 </script>';
2290         return format_admin_setting($this, $this->visiblename,
2291         '<div class="form-password"><input type="password" size="'.$this->size.'" id="'.$id.'" name="'.$this->get_full_name().'" value="'.s($data).'" /><div class="unmask" id="'.$id.'unmaskdiv"></div>'.$unmaskjs.'</div>',
2292         $this->description, true, '', NULL, $query);
2293     }
2296 /**
2297  * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
2298  * Note: Only advanced makes sense right now - locked does not.
2299  *
2300  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2301  */
2302 class admin_setting_configempty extends admin_setting_configtext {
2304     /**
2305      * @param string $name
2306      * @param string $visiblename
2307      * @param string $description
2308      */
2309     public function __construct($name, $visiblename, $description) {
2310         parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
2311     }
2313     /**
2314      * Returns an XHTML string for the hidden field
2315      *
2316      * @param string $data
2317      * @param string $query
2318      * @return string XHTML string for the editor
2319      */
2320     public function output_html($data, $query='') {
2321         return format_admin_setting($this,
2322                                     $this->visiblename,
2323                                     '<div class="form-empty" >' .
2324                                     '<input type="hidden"' .
2325                                         ' id="'. $this->get_id() .'"' .
2326                                         ' name="'. $this->get_full_name() .'"' .
2327                                         ' value=""/></div>',
2328                                     $this->description,
2329                                     true,
2330                                     '',
2331                                     get_string('none'),
2332                                     $query);
2333     }
2337 /**
2338  * Path to directory
2339  *
2340  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2341  */
2342 class admin_setting_configfile extends admin_setting_configtext {
2343     /**
2344      * Constructor
2345      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2346      * @param string $visiblename localised
2347      * @param string $description long localised info
2348      * @param string $defaultdirectory default directory location
2349      */
2350     public function __construct($name, $visiblename, $description, $defaultdirectory) {
2351         parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2352     }
2354     /**
2355      * Returns XHTML for the field
2356      *
2357      * Returns XHTML for the field and also checks whether the file
2358      * specified in $data exists using file_exists()
2359      *
2360      * @param string $data File name and path to use in value attr
2361      * @param string $query
2362      * @return string XHTML field
2363      */
2364     public function output_html($data, $query='') {
2365         $default = $this->get_defaultsetting();
2367         if ($data) {
2368             if (file_exists($data)) {
2369                 $executable = '<span class="pathok">&#x2714;</span>';
2370             } else {
2371                 $executable = '<span class="patherror">&#x2718;</span>';
2372             }
2373         } else {
2374             $executable = '';
2375         }
2377         return format_admin_setting($this, $this->visiblename,
2378         '<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
2379         $this->description, true, '', $default, $query);
2380     }
2381     /**
2382      * checks if execpatch has been disabled in config.php
2383      */
2384     public function write_setting($data) {
2385         global $CFG;
2386         if (!empty($CFG->preventexecpath)) {
2387             return '';
2388         }
2389         return parent::write_setting($data);
2390     }
2394 /**
2395  * Path to executable file
2396  *
2397  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2398  */
2399 class admin_setting_configexecutable extends admin_setting_configfile {
2401     /**
2402      * Returns an XHTML field
2403      *
2404      * @param string $data This is the value for the field
2405      * @param string $query
2406      * @return string XHTML field
2407      */
2408     public function output_html($data, $query='') {
2409         global $CFG;
2410         $default = $this->get_defaultsetting();
2412         if ($data) {
2413             if (file_exists($data) and is_executable($data)) {
2414                 $executable = '<span class="pathok">&#x2714;</span>';
2415             } else {
2416                 $executable = '<span class="patherror">&#x2718;</span>';
2417             }
2418         } else {
2419             $executable = '';
2420         }
2421         if (!empty($CFG->preventexecpath)) {
2422             $this->visiblename .= '<div class="form-overridden">'.get_string('execpathnotallowed', 'admin').'</div>';
2423         }
2425         return format_admin_setting($this, $this->visiblename,
2426         '<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
2427         $this->description, true, '', $default, $query);
2428     }
2432 /**
2433  * Path to directory
2434  *
2435  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2436  */
2437 class admin_setting_configdirectory extends admin_setting_configfile {
2439     /**
2440      * Returns an XHTML field
2441      *
2442      * @param string $data This is the value for the field
2443      * @param string $query
2444      * @return string XHTML
2445      */
2446     public function output_html($data, $query='') {
2447         $default = $this->get_defaultsetting();
2449         if ($data) {
2450             if (file_exists($data) and is_dir($data)) {
2451                 $executable = '<span class="pathok">&#x2714;</span>';
2452             } else {
2453                 $executable = '<span class="patherror">&#x2718;</span>';
2454             }
2455         } else {
2456             $executable = '';
2457         }
2459         return format_admin_setting($this, $this->visiblename,
2460         '<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
2461         $this->description, true, '', $default, $query);
2462     }
2466 /**
2467  * Checkbox
2468  *
2469  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2470  */
2471 class admin_setting_configcheckbox extends admin_setting {
2472     /** @var string Value used when checked */
2473     public $yes;
2474     /** @var string Value used when not checked */
2475     public $no;
2477     /**
2478      * Constructor
2479      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2480      * @param string $visiblename localised
2481      * @param string $description long localised info
2482      * @param string $defaultsetting
2483      * @param string $yes value used when checked
2484      * @param string $no value used when not checked
2485      */
2486     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2487         parent::__construct($name, $visiblename, $description, $defaultsetting);
2488         $this->yes = (string)$yes;
2489         $this->no  = (string)$no;
2490     }
2492     /**
2493      * Retrieves the current setting using the objects name
2494      *
2495      * @return string
2496      */
2497     public function get_setting() {
2498         return $this->config_read($this->name);
2499     }
2501     /**
2502      * Sets the value for the setting
2503      *
2504      * Sets the value for the setting to either the yes or no values
2505      * of the object by comparing $data to yes
2506      *
2507      * @param mixed $data Gets converted to str for comparison against yes value
2508      * @return string empty string or error
2509      */
2510     public function write_setting($data) {
2511         if ((string)$data === $this->yes) { // convert to strings before comparison
2512             $data = $this->yes;
2513         } else {
2514             $data = $this->no;
2515         }
2516         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2517     }
2519     /**
2520      * Returns an XHTML checkbox field
2521      *
2522      * @param string $data If $data matches yes then checkbox is checked
2523      * @param string $query
2524      * @return string XHTML field
2525      */
2526     public function output_html($data, $query='') {
2527         $default = $this->get_defaultsetting();
2529         if (!is_null($default)) {
2530             if ((string)$default === $this->yes) {
2531                 $defaultinfo = get_string('checkboxyes', 'admin');
2532             } else {
2533                 $defaultinfo = get_string('checkboxno', 'admin');
2534             }
2535         } else {
2536             $defaultinfo = NULL;
2537         }
2539         if ((string)$data === $this->yes) { // convert to strings before comparison
2540             $checked = 'checked="checked"';
2541         } else {
2542             $checked = '';
2543         }
2545         return format_admin_setting($this, $this->visiblename,
2546         '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2547             .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2548         $this->description, true, '', $defaultinfo, $query);
2549     }
2553 /**
2554  * Multiple checkboxes, each represents different value, stored in csv format
2555  *
2556  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2557  */
2558 class admin_setting_configmulticheckbox extends admin_setting {
2559     /** @var array Array of choices value=>label */
2560     public $choices;
2562     /**
2563      * Constructor: uses parent::__construct
2564      *
2565      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2566      * @param string $visiblename localised
2567      * @param string $description long localised info
2568      * @param array $defaultsetting array of selected
2569      * @param array $choices array of $value=>$label for each checkbox
2570      */
2571     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2572         $this->choices = $choices;
2573         parent::__construct($name, $visiblename, $description, $defaultsetting);
2574     }
2576     /**
2577      * This public function may be used in ancestors for lazy loading of choices
2578      *
2579      * @todo Check if this function is still required content commented out only returns true
2580      * @return bool true if loaded, false if error
2581      */
2582     public function load_choices() {
2583         /*
2584         if (is_array($this->choices)) {
2585             return true;
2586         }
2587         .... load choices here
2588         */
2589         return true;
2590     }
2592     /**
2593      * Is setting related to query text - used when searching
2594      *
2595      * @param string $query
2596      * @return bool true on related, false on not or failure
2597      */
2598     public function is_related($query) {
2599         if (!$this->load_choices() or empty($this->choices)) {
2600             return false;
2601         }
2602         if (parent::is_related($query)) {
2603             return true;
2604         }
2606         foreach ($this->choices as $desc) {
2607             if (strpos(core_text::strtolower($desc), $query) !== false) {
2608                 return true;
2609             }
2610         }
2611         return false;
2612     }
2614     /**
2615      * Returns the current setting if it is set
2616      *
2617      * @return mixed null if null, else an array
2618      */
2619     public function get_setting() {
2620         $result = $this->config_read($this->name);
2622         if (is_null($result)) {
2623             return NULL;
2624         }
2625         if ($result === '') {
2626             return array();
2627         }
2628         $enabled = explode(',', $result);
2629         $setting = array();
2630         foreach ($enabled as $option) {
2631             $setting[$option] = 1;
2632         }
2633         return $setting;
2634     }
2636     /**
2637      * Saves the setting(s) provided in $data
2638      *
2639      * @param array $data An array of data, if not array returns empty str
2640      * @return mixed empty string on useless data or bool true=success, false=failed
2641      */
2642     public function write_setting($data) {
2643         if (!is_array($data)) {
2644             return ''; // ignore it
2645         }
2646         if (!$this->load_choices() or empty($this->choices)) {
2647             return '';
2648         }
2649         unset($data['xxxxx']);
2650         $result = array();
2651         foreach ($data as $key => $value) {
2652             if ($value and array_key_exists($key, $this->choices)) {
2653                 $result[] = $key;
2654             }
2655         }
2656         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2657     }
2659     /**
2660      * Returns XHTML field(s) as required by choices
2661      *
2662      * Relies on data being an array should data ever be another valid vartype with
2663      * acceptable value this may cause a warning/error
2664      * if (!is_array($data)) would fix the problem
2665      *
2666      * @todo Add vartype handling to ensure $data is an array
2667      *
2668      * @param array $data An array of checked values
2669      * @param string $query
2670      * @return string XHTML field
2671      */
2672     public function output_html($data, $query='') {
2673         if (!$this->load_choices() or empty($this->choices)) {
2674             return '';
2675         }
2676         $default = $this->get_defaultsetting();
2677         if (is_null($default)) {
2678             $default = array();
2679         }
2680         if (is_null($data)) {
2681             $data = array();
2682         }
2683         $options = array();
2684         $defaults = array();
2685         foreach ($this->choices as $key=>$description) {
2686             if (!empty($data[$key])) {
2687                 $checked = 'checked="checked"';
2688             } else {
2689                 $checked = '';
2690             }
2691             if (!empty($default[$key])) {
2692                 $defaults[] = $description;
2693             }
2695             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
2696                 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
2697         }
2699         if (is_null($default)) {
2700             $defaultinfo = NULL;
2701         } else if (!empty($defaults)) {
2702                 $defaultinfo = implode(', ', $defaults);
2703             } else {
2704                 $defaultinfo = get_string('none');
2705             }
2707         $return = '<div class="form-multicheckbox">';
2708         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2709         if ($options) {
2710             $return .= '<ul>';
2711             foreach ($options as $option) {
2712                 $return .= '<li>'.$option.'</li>';
2713             }
2714             $return .= '</ul>';
2715         }
2716         $return .= '</div>';
2718         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2720     }
2724 /**
2725  * Multiple checkboxes 2, value stored as string 00101011
2726  *
2727  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2728  */
2729 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
2731     /**
2732      * Returns the setting if set
2733      *
2734      * @return mixed null if not set, else an array of set settings
2735      */
2736     public function get_setting() {
2737         $result = $this->config_read($this->name);
2738         if (is_null($result)) {
2739             return NULL;
2740         }
2741         if (!$this->load_choices()) {
2742             return NULL;
2743         }
2744         $result = str_pad($result, count($this->choices), '0');
2745         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2746         $setting = array();
2747         foreach ($this->choices as $key=>$unused) {
2748             $value = array_shift($result);
2749             if ($value) {
2750                 $setting[$key] = 1;
2751             }
2752         }
2753         return $setting;
2754     }
2756     /**
2757      * Save setting(s) provided in $data param
2758      *
2759      * @param array $data An array of settings to save
2760      * @return mixed empty string for bad data or bool true=>success, false=>error
2761      */
2762     public function write_setting($data) {
2763         if (!is_array($data)) {
2764             return ''; // ignore it
2765         }
2766         if (!$this->load_choices() or empty($this->choices)) {
2767             return '';
2768         }
2769         $result = '';
2770         foreach ($this->choices as $key=>$unused) {
2771             if (!empty($data[$key])) {
2772                 $result .= '1';
2773             } else {
2774                 $result .= '0';
2775             }
2776         }
2777         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2778     }
2782 /**
2783  * Select one value from list
2784  *
2785  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2786  */
2787 class admin_setting_configselect extends admin_setting {
2788     /** @var array Array of choices value=>label */
2789     public $choices;
2791     /**
2792      * Constructor
2793      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2794      * @param string $visiblename localised
2795      * @param string $description long localised info
2796      * @param string|int $defaultsetting
2797      * @param array $choices array of $value=>$label for each selection
2798      */
2799     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2800         $this->choices = $choices;
2801         parent::__construct($name, $visiblename, $description, $defaultsetting);
2802     }
2804     /**
2805      * This function may be used in ancestors for lazy loading of choices
2806      *
2807      * Override this method if loading of choices is expensive, such
2808      * as when it requires multiple db requests.
2809      *
2810      * @return bool true if loaded, false if error
2811      */
2812     public function load_choices() {
2813         /*
2814         if (is_array($this->choices)) {
2815             return true;
2816         }
2817         .... load choices here
2818         */
2819         return true;
2820     }
2822     /**
2823      * Check if this is $query is related to a choice
2824      *
2825      * @param string $query
2826      * @return bool true if related, false if not
2827      */
2828     public function is_related($query) {
2829         if (parent::is_related($query)) {
2830             return true;
2831         }
2832         if (!$this->load_choices()) {
2833             return false;
2834         }
2835         foreach ($this->choices as $key=>$value) {
2836             if (strpos(core_text::strtolower($key), $query) !== false) {
2837                 return true;
2838             }
2839             if (strpos(core_text::strtolower($value), $query) !== false) {
2840                 return true;
2841             }
2842         }
2843         return false;
2844     }
2846     /**
2847      * Return the setting
2848      *
2849      * @return mixed returns config if successful else null
2850      */
2851     public function get_setting() {
2852         return $this->config_read($this->name);
2853     }
2855     /**
2856      * Save a setting
2857      *
2858      * @param string $data
2859      * @return string empty of error string
2860      */
2861     public function write_setting($data) {
2862         if (!$this->load_choices() or empty($this->choices)) {
2863             return '';
2864         }
2865         if (!array_key_exists($data, $this->choices)) {
2866             return ''; // ignore it
2867         }
2869         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2870     }
2872     /**
2873      * Returns XHTML select field
2874      *
2875      * Ensure the options are loaded, and generate the XHTML for the select
2876      * element and any warning message. Separating this out from output_html
2877      * makes it easier to subclass this class.
2878      *
2879      * @param string $data the option to show as selected.
2880      * @param string $current the currently selected option in the database, null if none.
2881      * @param string $default the default selected option.
2882      * @return array the HTML for the select element, and a warning message.
2883      */
2884     public function output_select_html($data, $current, $default, $extraname = '') {
2885         if (!$this->load_choices() or empty($this->choices)) {
2886             return array('', '');
2887         }
2889         $warning = '';
2890         if (is_null($current)) {
2891         // first run
2892         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2893             // no warning
2894             } else if (!array_key_exists($current, $this->choices)) {
2895                     $warning = get_string('warningcurrentsetting', 'admin', s($current));
2896                     if (!is_null($default) and $data == $current) {
2897                         $data = $default; // use default instead of first value when showing the form
2898                     }
2899                 }
2901         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
2902         foreach ($this->choices as $key => $value) {
2903         // the string cast is needed because key may be integer - 0 is equal to most strings!
2904             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
2905         }
2906         $selecthtml .= '</select>';
2907         return array($selecthtml, $warning);
2908     }
2910     /**
2911      * Returns XHTML select field and wrapping div(s)
2912      *
2913      * @see output_select_html()
2914      *
2915      * @param string $data the option to show as selected
2916      * @param string $query
2917      * @return string XHTML field and wrapping div
2918      */
2919     public function output_html($data, $query='') {
2920         $default = $this->get_defaultsetting();
2921         $current = $this->get_setting();
2923         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2924         if (!$selecthtml) {
2925             return '';
2926         }
2928         if (!is_null($default) and array_key_exists($default, $this->choices)) {
2929             $defaultinfo = $this->choices[$default];
2930         } else {
2931             $defaultinfo = NULL;
2932         }
2934         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
2936         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
2937     }
2941 /**
2942  * Select multiple items from list
2943  *
2944  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2945  */
2946 class admin_setting_configmultiselect extends admin_setting_configselect {
2947     /**
2948      * Constructor
2949      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2950      * @param string $visiblename localised
2951      * @param string $description long localised info
2952      * @param array $defaultsetting array of selected items
2953      * @param array $choices array of $value=>$label for each list item
2954      */
2955     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2956         parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
2957     }
2959     /**
2960      * Returns the select setting(s)
2961      *
2962      * @return mixed null or array. Null if no settings else array of setting(s)
2963      */
2964     public function get_setting() {
2965         $result = $this->config_read($this->name);
2966         if (is_null($result)) {
2967             return NULL;
2968         }
2969         if ($result === '') {
2970             return array();
2971         }
2972         return explode(',', $result);
2973     }
2975     /**
2976      * Saves setting(s) provided through $data
2977      *
2978      * Potential bug in the works should anyone call with this function
2979      * using a vartype that is not an array
2980      *
2981      * @param array $data
2982      */
2983     public function write_setting($data) {
2984         if (!is_array($data)) {
2985             return ''; //ignore it
2986         }
2987         if (!$this->load_choices() or empty($this->choices)) {
2988             return '';
2989         }
2991         unset($data['xxxxx']);
2993         $save = array();
2994         foreach ($data as $value) {
2995             if (!array_key_exists($value, $this->choices)) {
2996                 continue; // ignore it
2997             }
2998             $save[] = $value;
2999         }
3001         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3002     }
3004     /**
3005      * Is setting related to query text - used when searching
3006      *
3007      * @param string $query
3008      * @return bool true if related, false if not
3009      */
3010     public function is_related($query) {
3011         if (!$this->load_choices() or empty($this->choices)) {
3012             return false;
3013         }
3014         if (parent::is_related($query)) {
3015             return true;
3016         }
3018         foreach ($this->choices as $desc) {
3019             if (strpos(core_text::strtolower($desc), $query) !== false) {
3020                 return true;
3021             }
3022         }
3023         return false;
3024     }
3026     /**
3027      * Returns XHTML multi-select field
3028      *
3029      * @todo Add vartype handling to ensure $data is an array
3030      * @param array $data Array of values to select by default
3031      * @param string $query
3032      * @return string XHTML multi-select field
3033      */
3034     public function output_html($data, $query='') {
3035         if (!$this->load_choices() or empty($this->choices)) {
3036             return '';
3037         }
3038         $choices = $this->choices;
3039         $default = $this->get_defaultsetting();
3040         if (is_null($default)) {
3041             $default = array();
3042         }
3043         if (is_null($data)) {
3044             $data = array();
3045         }
3047         $defaults = array();
3048         $size = min(10, count($this->choices));
3049         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3050         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
3051         foreach ($this->choices as $key => $description) {
3052             if (in_array($key, $data)) {
3053                 $selected = 'selected="selected"';
3054             } else {
3055                 $selected = '';
3056             }
3057             if (in_array($key, $default)) {
3058                 $defaults[] = $description;
3059             }
3061             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
3062         }
3064         if (is_null($default)) {
3065             $defaultinfo = NULL;
3066         } if (!empty($defaults)) {
3067             $defaultinfo = implode(', ', $defaults);
3068         } else {
3069             $defaultinfo = get_string('none');
3070         }
3072         $return .= '</select></div>';
3073         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
3074     }
3077 /**
3078  * Time selector
3079  *
3080  * This is a liiitle bit messy. we're using two selects, but we're returning
3081  * them as an array named after $name (so we only use $name2 internally for the setting)
3082  *
3083  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3084  */
3085 class admin_setting_configtime extends admin_setting {
3086     /** @var string Used for setting second select (minutes) */
3087     public $name2;
3089     /**
3090      * Constructor
3091      * @param string $hoursname setting for hours
3092      * @param string $minutesname setting for hours
3093      * @param string $visiblename localised
3094      * @param string $description long localised info
3095      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3096      */
3097     public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
3098         $this->name2 = $minutesname;
3099         parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
3100     }
3102     /**
3103      * Get the selected time
3104      *
3105      * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
3106      */
3107     public function get_setting() {
3108         $result1 = $this->config_read($this->name);
3109         $result2 = $this->config_read($this->name2);
3110         if (is_null($result1) or is_null($result2)) {
3111             return NULL;
3112         }
3114         return array('h' => $result1, 'm' => $result2);
3115     }
3117     /**
3118      * Store the time (hours and minutes)
3119      *
3120      * @param array $data Must be form 'h'=>xx, 'm'=>xx
3121      * @return bool true if success, false if not
3122      */
3123     public function write_setting($data) {
3124         if (!is_array($data)) {
3125             return '';
3126         }
3128         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3129         return ($result ? '' : get_string('errorsetting', 'admin'));
3130     }
3132     /**
3133      * Returns XHTML time select fields
3134      *
3135      * @param array $data Must be form 'h'=>xx, 'm'=>xx
3136      * @param string $query
3137      * @return string XHTML time select fields and wrapping div(s)
3138      */
3139     public function output_html($data, $query='') {
3140         $default = $this->get_defaultsetting();
3142         if (is_array($default)) {
3143             $defaultinfo = $default['h'].':'.$default['m'];
3144         } else {
3145             $defaultinfo = NULL;
3146         }
3148         $return = '<div class="form-time defaultsnext">'.
3149             '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
3150         for ($i = 0; $i < 24; $i++) {
3151             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
3152         }
3153         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
3154         for ($i = 0; $i < 60; $i += 5) {
3155             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
3156         }
3157         $return .= '</select></div>';
3158         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
3159     }
3164 /**
3165  * Seconds duration setting.
3166  *
3167  * @copyright 2012 Petr Skoda (http://skodak.org)
3168  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3169  */
3170 class admin_setting_configduration extends admin_setting {
3172     /** @var int default duration unit */
3173     protected $defaultunit;
3175     /**
3176      * Constructor
3177      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3178      *                     or 'myplugin/mysetting' for ones in config_plugins.
3179      * @param string $visiblename localised name
3180      * @param string $description localised long description
3181      * @param mixed $defaultsetting string or array depending on implementation
3182      * @param int $defaultunit - day, week, etc. (in seconds)
3183      */
3184     public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3185         if (is_number($defaultsetting)) {
3186             $defaultsetting = self::parse_seconds($defaultsetting);
3187         }
3188         $units = self::get_units();
3189         if (isset($units[$defaultunit])) {
3190             $this->defaultunit = $defaultunit;
3191         } else {
3192             $this->defaultunit = 86400;
3193         }
3194         parent::__construct($name, $visiblename, $description, $defaultsetting);
3195     }
3197     /**
3198      * Returns selectable units.
3199      * @static
3200      * @return array
3201      */
3202     protected static function get_units() {
3203         return array(
3204             604800 => get_string('weeks'),
3205             86400 => get_string('days'),
3206             3600 => get_string('hours'),
3207             60 => get_string('minutes'),
3208             1 => get_string('seconds'),
3209         );
3210     }
3212     /**
3213      * Converts seconds to some more user friendly string.
3214      * @static
3215      * @param int $seconds
3216      * @return string
3217      */
3218     protected static function get_duration_text($seconds) {
3219         if (empty($seconds)) {
3220             return get_string('none');
3221         }
3222         $data = self::parse_seconds($seconds);
3223         switch ($data['u']) {
3224             case (60*60*24*7):
3225                 return get_string('numweeks', '', $data['v']);
3226             case (60*60*24):
3227                 return get_string('numdays', '', $data['v']);
3228             case (60*60):
3229                 return get_string('numhours', '', $data['v']);
3230             case (60):
3231                 return get_string('numminutes', '', $data['v']);
3232             default:
3233                 return get_string('numseconds', '', $data['v']*$data['u']);
3234         }
3235     }
3237     /**
3238      * Finds suitable units for given duration.
3239      * @static
3240      * @param int $seconds
3241      * @return array
3242      */
3243     protected static function parse_seconds($seconds) {
3244         foreach (self::get_units() as $unit => $unused) {
3245             if ($seconds % $unit === 0) {
3246                 return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
3247             }
3248         }
3249         return array('v'=>(int)$seconds, 'u'=>1);
3250     }
3252     /**
3253      * Get the selected duration as array.
3254      *
3255      * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
3256      */
3257     public function get_setting() {
3258         $seconds = $this->config_read($this->name);
3259         if (is_null($seconds)) {
3260             return null;
3261         }
3263         return self::parse_seconds($seconds);
3264     }
3266     /**
3267      * Store the duration as seconds.
3268      *
3269      * @param array $data Must be form 'h'=>xx, 'm'=>xx
3270      * @return bool true if success, false if not
3271      */
3272     public function write_setting($data) {
3273         if (!is_array($data)) {
3274             return '';
3275         }
3277         $seconds = (int)($data['v']*$data['u']);
3278         if ($seconds < 0) {
3279             return get_string('errorsetting', 'admin');
3280         }
3282         $result = $this->config_write($this->name, $seconds);
3283         return ($result ? '' : get_string('errorsetting', 'admin'));
3284     }
3286     /**
3287      * Returns duration text+select fields.
3288      *
3289      * @param array $data Must be form 'v'=>xx, 'u'=>xx
3290      * @param string $query
3291      * @return string duration text+select fields and wrapping div(s)
3292      */
3293     public function output_html($data, $query='') {
3294         $default = $this->get_defaultsetting();
3296         if (is_number($default)) {
3297             $defaultinfo = self::get_duration_text($default);
3298         } else if (is_array($default)) {
3299             $defaultinfo = self::get_duration_text($default['v']*$default['u']);
3300         } else {
3301             $defaultinfo = null;
3302         }
3304         $units = self::get_units();
3306         $return = '<div class="form-duration defaultsnext">';
3307         $return .= '<input type="text" size="5" id="'.$this->get_id().'v" name="'.$this->get_full_name().'[v]" value="'.s($data['v']).'" />';
3308         $return .= '<select id="'.$this->get_id().'u" name="'.$this->get_full_name().'[u]">';
3309         foreach ($units as $val => $text) {
3310             $selected = '';
3311             if ($data['v'] == 0) {
3312                 if ($val == $this->defaultunit) {
3313                     $selected = ' selected="selected"';
3314                 }
3315             } else if ($val == $data['u']) {
3316                 $selected = ' selected="selected"';
3317             }
3318             $return .= '<option value="'.$val.'"'.$selected.'>'.$text.'</option>';
3319         }
3320         $return .= '</select></div>';
3321         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
3322     }
3326 /**
3327  * Used to validate a textarea used for ip addresses
3328  *
3329  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3330  */
3331 class admin_setting_configiplist extends admin_setting_configtextarea {
3333     /**
3334      * Validate the contents of the textarea as IP addresses
3335      *
3336      * Used to validate a new line separated list of IP addresses collected from
3337      * a textarea control
3338      *
3339      * @param string $data A list of IP Addresses separated by new lines
3340      * @return mixed bool true for success or string:error on failure
3341      */
3342     public function validate($data) {
3343         if(!empty($data)) {
3344             $ips = explode("\n", $data);
3345         } else {
3346             return true;
3347         }
3348         $result = true;
3349         foreach($ips as $ip) {
3350             $ip = trim($ip);
3351             if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
3352                 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
3353                 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
3354                 $result = true;
3355             } else {
3356                 $result = false;
3357                 break;
3358             }
3359         }
3360         if($result) {
3361             return true;
3362         } else {
3363             return get_string('validateerror', 'admin');
3364         }
3365     }
3369 /**
3370  * An admin setting for selecting one or more users who have a capability
3371  * in the system context
3372  *
3373  * An admin setting for selecting one or more users, who have a particular capability
3374  * in the system context. Warning, make sure the list will never be too long. There is
3375  * no paging or searching of this list.
3376  *
3377  * To correctly get a list of users from this config setting, you need to call the
3378  * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
3379  *
3380  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3381  */
3382 class admin_setting_users_with_capability extends admin_setting_configmultiselect {
3383     /** @var string The capabilities name */
3384     protected $capability;
3385     /** @var int include admin users too */
3386     protected $includeadmins;
3388     /**
3389      * Constructor.
3390      *
3391      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3392      * @param string $visiblename localised name
3393      * @param string $description localised long description
3394      * @param array $defaultsetting array of usernames
3395      * @param string $capability string capability name.
3396      * @param bool $includeadmins include administrators
3397      */
3398     function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
3399         $this->capability    = $capability;
3400         $this->includeadmins = $includeadmins;
3401         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
3402     }
3404     /**
3405      * Load all of the uses who have the capability into choice array
3406      *
3407      * @return bool Always returns true
3408      */
3409     function load_choices() {
3410         if (is_array($this->choices)) {
3411             return true;
3412         }
3413         list($sort, $sortparams) = users_order_by_sql('u');
3414         if (!empty($sortparams)) {
3415             throw new coding_exception('users_order_by_sql returned some query parameters. ' .
3416                     'This is unexpected, and a problem because there is no way to pass these ' .
3417                     'parameters to get_users_by_capability. See MDL-34657.');
3418         }
3419         $users = get_users_by_capability(context_system::instance(),
3420                 $this->capability, 'u.id,u.username,u.firstname,u.lastname', $sort);
3421         $this->choices = array(
3422             '$@NONE@$' => get_string('nobody'),
3423             '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
3424         );
3425         if ($this->includeadmins) {
3426             $admins = get_admins();
3427             foreach ($admins as $user) {
3428                 $this->choices[$user->id] = fullname($user);
3429             }
3430         }
3431         if (is_array($users)) {
3432             foreach ($users as $user) {
3433                 $this->choices[$user->id] = fullname($user);
3434             }
3435         }
3436         return true;
3437     }
3439     /**
3440      * Returns the default setting for class
3441      *
3442      * @return mixed Array, or string. Empty string if no default
3443      */
3444     public function get_defaultsetting() {
3445         $this->load_choices();
3446         $defaultsetting = parent::get_defaultsetting();
3447         if (empty($defaultsetting)) {
3448             return array('$@NONE@$');
3449         } else if (array_key_exists($defaultsetting, $this->choices)) {
3450                 return $defaultsetting;
3451             } else {
3452                 return '';
3453             }
3454     }
3456     /**
3457      * Returns the current setting
3458      *
3459      * @return mixed array or string
3460      */
3461     public function get_setting() {
3462         $result = parent::get_setting();
3463         if ($result === null) {
3464             // this is necessary for settings upgrade
3465             return null;
3466         }
3467         if (empty($result)) {
3468             $result = array('$@NONE@$');
3469         }
3470         return $result;
3471     }
3473     /**
3474      * Save the chosen setting provided as $data
3475      *
3476      * @param array $data
3477      * @return mixed string or array
3478      */
3479     public function write_setting($data) {
3480     // If all is selected, remove any explicit options.
3481         if (in_array('$@ALL@$', $data)) {
3482             $data = array('$@ALL@$');
3483         }
3484         // None never needs to be written to the DB.
3485         if (in_array('$@NONE@$', $data)) {
3486             unset($data[array_search('$@NONE@$', $data)]);
3487         }
3488         return parent::write_setting($data);
3489     }
3493 /**
3494  * Special checkbox for calendar - resets SESSION vars.
3495  *
3496  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3497  */
3498 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
3499     /**
3500      * Calls the parent::__construct with default values
3501      *
3502      * name =>  calendar_adminseesall
3503      * visiblename => get_string('adminseesall', 'admin')
3504      * description => get_string('helpadminseesall', 'admin')
3505      * defaultsetting => 0
3506      */
3507     public function __construct() {
3508         parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
3509             get_string('helpadminseesall', 'admin'), '0');
3510     }
3512     /**
3513      * Stores the setting passed in $data
3514      *
3515      * @param mixed gets converted to string for comparison
3516      * @return string empty string or error message
3517      */
3518     public function write_setting($data) {
3519         global $SESSION;
3520         return parent::write_setting($data);
3521     }
3524 /**
3525  * Special select for settings that are altered in setup.php and can not be altered on the fly
3526  *
3527  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3528  */
3529 class admin_setting_special_selectsetup extends admin_setting_configselect {
3530     /**
3531      * Reads the setting directly from the database
3532      *
3533      * @return mixed
3534      */
3535     public function get_setting() {
3536     // read directly from db!
3537         return get_config(NULL, $this->name);
3538     }
3540     /**
3541      * Save the setting passed in $data
3542      *
3543      * @param string $data The setting to save
3544      * @return string empty or error message
3545      */
3546     public function write_setting($data) {
3547         global $CFG;
3548         // do not change active CFG setting!
3549         $current = $CFG->{$this->name};
3550         $result = parent::write_setting($data);
3551         $CFG->{$this->name} = $current;
3552         return $result;
3553     }
3557 /**
3558  * Special select for frontpage - stores data in course table
3559  *
3560  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3561  */
3562 class admin_setting_sitesetselect extends admin_setting_configselect {
3563     /**
3564      * Returns the site name for the selected site
3565      *
3566      * @see get_site()
3567      * @return string The site name of the selected site
3568      */
3569     public function get_setting() {
3570         $site = course_get_format(get_site())->get_course();
3571         return $site->{$this->name};
3572     }
3574     /**
3575      * Updates the database and save the setting
3576      *
3577      * @param string data
3578      * @return string empty or error message
3579      */
3580     public function write_setting($data) {
3581         global $DB, $SITE, $COURSE;
3582         if (!in_array($data, array_keys($this->choices))) {
3583             return get_string('errorsetting', 'admin');
3584         }
3585         $record = new stdClass();
3586         $record->id           = SITEID;
3587         $temp                 = $this->name;
3588         $record->$temp        = $data;
3589         $record->timemodified = time();
3591         course_get_format($SITE)->update_course_format_options($record);
3592         $DB->update_record('course', $record);
3594         // Reset caches.
3595         $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST);
3596         if ($SITE->id == $COURSE->id) {
3597             $COURSE = $SITE;
3598         }
3599         format_base::reset_course_cache($SITE->id);
3601         return '';
3603     }
3607 /**
3608  * Select for blog's bloglevel setting: if set to 0, will set blog_menu
3609  * block to hidden.
3610  *
3611  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3612  */
3613 class admin_setting_bloglevel extends admin_setting_configselect {
3614     /**
3615      * Updates the database and save the setting
3616      *
3617      * @param string data
3618      * @return string empty or error message
3619      */
3620     public function write_setting($data) {
3621         global $DB, $CFG;
3622         if ($data == 0) {
3623             $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
3624             foreach ($blogblocks as $block) {
3625                 $DB->set_field('block', 'visible', 0, array('id' => $block->id));
3626             }
3627         } else {
3628             // reenable all blocks only when switching from disabled blogs
3629             if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
3630                 $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
3631                 foreach ($blogblocks as $block) {
3632                     $DB->set_field('block', 'visible', 1, array('id' => $block->id));
3633                 }
3634             }
3635         }
3636         return parent::write_setting($data);
3637     }
3641 /**
3642  * Special select - lists on the frontpage - hacky
3643  *
3644  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3645  */
3646 class admin_setting_courselist_frontpage extends admin_setting {
3647     /** @var array Array of choices value=>label */
3648     public $choices;
3650     /**
3651      * Construct override, requires one param
3652      *
3653      * @param bool $loggedin Is the user logged in
3654      */
3655     public function __construct($loggedin) {
3656         global $CFG;
3657         require_once($CFG->dirroot.'/course