MDL-39087 Delete all component files in uninstall_plugin()
[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  * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
119  * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
120  * @uses global $OUTPUT to produce notices and other messages
121  * @return void
122  */
123 function uninstall_plugin($type, $name) {
124     global $CFG, $DB, $OUTPUT;
126     // This may take a long time.
127     @set_time_limit(0);
129     // recursively uninstall all module/editor subplugins first
130     if ($type === 'mod' || $type === 'editor') {
131         $base = get_component_directory($type . '_' . $name);
132         if (file_exists("$base/db/subplugins.php")) {
133             $subplugins = array();
134             include("$base/db/subplugins.php");
135             foreach ($subplugins as $subplugintype=>$dir) {
136                 $instances = get_plugin_list($subplugintype);
137                 foreach ($instances as $subpluginname => $notusedpluginpath) {
138                     uninstall_plugin($subplugintype, $subpluginname);
139                 }
140             }
141         }
143     }
145     $component = $type . '_' . $name;  // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
147     if ($type === 'mod') {
148         $pluginname = $name;  // eg. 'forum'
149         if (get_string_manager()->string_exists('modulename', $component)) {
150             $strpluginname = get_string('modulename', $component);
151         } else {
152             $strpluginname = $component;
153         }
155     } else {
156         $pluginname = $component;
157         if (get_string_manager()->string_exists('pluginname', $component)) {
158             $strpluginname = get_string('pluginname', $component);
159         } else {
160             $strpluginname = $component;
161         }
162     }
164     echo $OUTPUT->heading($pluginname);
166     $plugindirectory = get_plugin_directory($type, $name);
167     $uninstalllib = $plugindirectory . '/db/uninstall.php';
168     if (file_exists($uninstalllib)) {
169         require_once($uninstalllib);
170         $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall';    // eg. 'xmldb_workshop_uninstall()'
171         if (function_exists($uninstallfunction)) {
172             if (!$uninstallfunction()) {
173                 echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $pluginname);
174             }
175         }
176     }
178     if ($type === 'mod') {
179         // perform cleanup tasks specific for activity modules
181         if (!$module = $DB->get_record('modules', array('name' => $name))) {
182             print_error('moduledoesnotexist', 'error');
183         }
185         // delete all the relevant instances from all course sections
186         if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) {
187             foreach ($coursemods as $coursemod) {
188                 if (!delete_mod_from_section($coursemod->id, $coursemod->section)) {
189                     echo $OUTPUT->notification("Could not delete the $strpluginname with id = $coursemod->id from section $coursemod->section");
190                 }
191             }
192         }
194         // clear course.modinfo for courses that used this module
195         $sql = "UPDATE {course}
196                    SET modinfo=''
197                  WHERE id IN (SELECT DISTINCT course
198                                 FROM {course_modules}
199                                WHERE module=?)";
200         $DB->execute($sql, array($module->id));
202         // delete all the course module records
203         $DB->delete_records('course_modules', array('module' => $module->id));
205         // delete module contexts
206         if ($coursemods) {
207             foreach ($coursemods as $coursemod) {
208                 if (!delete_context(CONTEXT_MODULE, $coursemod->id)) {
209                     echo $OUTPUT->notification("Could not delete the context for $strpluginname with id = $coursemod->id");
210                 }
211             }
212         }
214         // delete the module entry itself
215         $DB->delete_records('modules', array('name' => $module->name));
217         // cleanup the gradebook
218         require_once($CFG->libdir.'/gradelib.php');
219         grade_uninstalled_module($module->name);
221         // Perform any custom uninstall tasks
222         if (file_exists($CFG->dirroot . '/mod/' . $module->name . '/lib.php')) {
223             require_once($CFG->dirroot . '/mod/' . $module->name . '/lib.php');
224             $uninstallfunction = $module->name . '_uninstall';
225             if (function_exists($uninstallfunction)) {
226                 debugging("{$uninstallfunction}() has been deprecated. Use the plugin's db/uninstall.php instead", DEBUG_DEVELOPER);
227                 if (!$uninstallfunction()) {
228                     echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $module->name.'!');
229                 }
230             }
231         }
233     } else if ($type === 'enrol') {
234         // NOTE: this is a bit brute force way - it will not trigger events and hooks properly
235         // nuke all role assignments
236         role_unassign_all(array('component'=>$component));
237         // purge participants
238         $DB->delete_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($name));
239         // purge enrol instances
240         $DB->delete_records('enrol', array('enrol'=>$name));
241         // tweak enrol settings
242         if (!empty($CFG->enrol_plugins_enabled)) {
243             $enabledenrols = explode(',', $CFG->enrol_plugins_enabled);
244             $enabledenrols = array_unique($enabledenrols);
245             $enabledenrols = array_flip($enabledenrols);
246             unset($enabledenrols[$name]);
247             $enabledenrols = array_flip($enabledenrols);
248             if (is_array($enabledenrols)) {
249                 set_config('enrol_plugins_enabled', implode(',', $enabledenrols));
250             }
251         }
253     } else if ($type === 'block') {
254         if ($block = $DB->get_record('block', array('name'=>$name))) {
255             // Inform block it's about to be deleted
256             if (file_exists("$CFG->dirroot/blocks/$block->name/block_$block->name.php")) {
257                 $blockobject = block_instance($block->name);
258                 if ($blockobject) {
259                     $blockobject->before_delete();  //only if we can create instance, block might have been already removed
260                 }
261             }
263             // First delete instances and related contexts
264             $instances = $DB->get_records('block_instances', array('blockname' => $block->name));
265             foreach($instances as $instance) {
266                 blocks_delete_instance($instance);
267             }
269             // Delete block
270             $DB->delete_records('block', array('id'=>$block->id));
271         }
272     } else if ($type === 'format') {
273         if (($defaultformat = get_config('moodlecourse', 'format')) && $defaultformat !== $name) {
274             $courses = $DB->get_records('course', array('format' => $name), 'id');
275             $data = (object)array('id' => null, 'format' => $defaultformat);
276             foreach ($courses as $record) {
277                 $data->id = $record->id;
278                 update_course($data);
279             }
280         }
281         $DB->delete_records('course_format_options', array('format' => $name));
282     }
284     // perform clean-up task common for all the plugin/subplugin types
286     //delete the web service functions and pre-built services
287     require_once($CFG->dirroot.'/lib/externallib.php');
288     external_delete_descriptions($component);
290     // delete calendar events
291     $DB->delete_records('event', array('modulename' => $pluginname));
293     // delete all the logs
294     $DB->delete_records('log', array('module' => $pluginname));
296     // delete log_display information
297     $DB->delete_records('log_display', array('component' => $component));
299     // delete the module configuration records
300     unset_all_config_for_plugin($pluginname);
302     // delete message provider
303     message_provider_uninstall($component);
305     // delete message processor
306     if ($type === 'message') {
307         message_processor_uninstall($name);
308     }
310     // delete the plugin tables
311     $xmldbfilepath = $plugindirectory . '/db/install.xml';
312     drop_plugin_tables($component, $xmldbfilepath, false);
313     if ($type === 'mod' or $type === 'block') {
314         // non-frankenstyle table prefixes
315         drop_plugin_tables($name, $xmldbfilepath, false);
316     }
318     // delete the capabilities that were defined by this module
319     capabilities_cleanup($component);
321     // remove event handlers and dequeue pending events
322     events_uninstall($component);
324     // Delete all remaining files in the filepool owned by the component.
325     $fs = get_file_storage();
326     $fs->delete_component_files($component);
328     // Finally purge all caches.
329     purge_all_caches();
331     echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
334 /**
335  * Returns the version of installed component
336  *
337  * @param string $component component name
338  * @param string $source either 'disk' or 'installed' - where to get the version information from
339  * @return string|bool version number or false if the component is not found
340  */
341 function get_component_version($component, $source='installed') {
342     global $CFG, $DB;
344     list($type, $name) = normalize_component($component);
346     // moodle core or a core subsystem
347     if ($type === 'core') {
348         if ($source === 'installed') {
349             if (empty($CFG->version)) {
350                 return false;
351             } else {
352                 return $CFG->version;
353             }
354         } else {
355             if (!is_readable($CFG->dirroot.'/version.php')) {
356                 return false;
357             } else {
358                 $version = null; //initialize variable for IDEs
359                 include($CFG->dirroot.'/version.php');
360                 return $version;
361             }
362         }
363     }
365     // activity module
366     if ($type === 'mod') {
367         if ($source === 'installed') {
368             return $DB->get_field('modules', 'version', array('name'=>$name));
369         } else {
370             $mods = get_plugin_list('mod');
371             if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
372                 return false;
373             } else {
374                 $module = new stdclass();
375                 include($mods[$name].'/version.php');
376                 return $module->version;
377             }
378         }
379     }
381     // block
382     if ($type === 'block') {
383         if ($source === 'installed') {
384             return $DB->get_field('block', 'version', array('name'=>$name));
385         } else {
386             $blocks = get_plugin_list('block');
387             if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
388                 return false;
389             } else {
390                 $plugin = new stdclass();
391                 include($blocks[$name].'/version.php');
392                 return $plugin->version;
393             }
394         }
395     }
397     // all other plugin types
398     if ($source === 'installed') {
399         return get_config($type.'_'.$name, 'version');
400     } else {
401         $plugins = get_plugin_list($type);
402         if (empty($plugins[$name])) {
403             return false;
404         } else {
405             $plugin = new stdclass();
406             include($plugins[$name].'/version.php');
407             return $plugin->version;
408         }
409     }
412 /**
413  * Delete all plugin tables
414  *
415  * @param string $name Name of plugin, used as table prefix
416  * @param string $file Path to install.xml file
417  * @param bool $feedback defaults to true
418  * @return bool Always returns true
419  */
420 function drop_plugin_tables($name, $file, $feedback=true) {
421     global $CFG, $DB;
423     // first try normal delete
424     if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
425         return true;
426     }
428     // then try to find all tables that start with name and are not in any xml file
429     $used_tables = get_used_table_names();
431     $tables = $DB->get_tables();
433     /// Iterate over, fixing id fields as necessary
434     foreach ($tables as $table) {
435         if (in_array($table, $used_tables)) {
436             continue;
437         }
439         if (strpos($table, $name) !== 0) {
440             continue;
441         }
443         // found orphan table --> delete it
444         if ($DB->get_manager()->table_exists($table)) {
445             $xmldb_table = new xmldb_table($table);
446             $DB->get_manager()->drop_table($xmldb_table);
447         }
448     }
450     return true;
453 /**
454  * Returns names of all known tables == tables that moodle knows about.
455  *
456  * @return array Array of lowercase table names
457  */
458 function get_used_table_names() {
459     $table_names = array();
460     $dbdirs = get_db_directories();
462     foreach ($dbdirs as $dbdir) {
463         $file = $dbdir.'/install.xml';
465         $xmldb_file = new xmldb_file($file);
467         if (!$xmldb_file->fileExists()) {
468             continue;
469         }
471         $loaded    = $xmldb_file->loadXMLStructure();
472         $structure = $xmldb_file->getStructure();
474         if ($loaded and $tables = $structure->getTables()) {
475             foreach($tables as $table) {
476                 $table_names[] = strtolower($table->getName());
477             }
478         }
479     }
481     return $table_names;
484 /**
485  * Returns list of all directories where we expect install.xml files
486  * @return array Array of paths
487  */
488 function get_db_directories() {
489     global $CFG;
491     $dbdirs = array();
493     /// First, the main one (lib/db)
494     $dbdirs[] = $CFG->libdir.'/db';
496     /// Then, all the ones defined by get_plugin_types()
497     $plugintypes = get_plugin_types();
498     foreach ($plugintypes as $plugintype => $pluginbasedir) {
499         if ($plugins = get_plugin_list($plugintype)) {
500             foreach ($plugins as $plugin => $plugindir) {
501                 $dbdirs[] = $plugindir.'/db';
502             }
503         }
504     }
506     return $dbdirs;
509 /**
510  * Try to obtain or release the cron lock.
511  * @param string  $name  name of lock
512  * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionally
513  * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
514  * @return bool true if lock obtained
515  */
516 function set_cron_lock($name, $until, $ignorecurrent=false) {
517     global $DB;
518     if (empty($name)) {
519         debugging("Tried to get a cron lock for a null fieldname");
520         return false;
521     }
523     // remove lock by force == remove from config table
524     if (is_null($until)) {
525         set_config($name, null);
526         return true;
527     }
529     if (!$ignorecurrent) {
530         // read value from db - other processes might have changed it
531         $value = $DB->get_field('config', 'value', array('name'=>$name));
533         if ($value and $value > time()) {
534             //lock active
535             return false;
536         }
537     }
539     set_config($name, $until);
540     return true;
543 /**
544  * Test if and critical warnings are present
545  * @return bool
546  */
547 function admin_critical_warnings_present() {
548     global $SESSION;
550     if (!has_capability('moodle/site:config', context_system::instance())) {
551         return 0;
552     }
554     if (!isset($SESSION->admin_critical_warning)) {
555         $SESSION->admin_critical_warning = 0;
556         if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
557             $SESSION->admin_critical_warning = 1;
558         }
559     }
561     return $SESSION->admin_critical_warning;
564 /**
565  * Detects if float supports at least 10 decimal digits
566  *
567  * Detects if float supports at least 10 decimal digits
568  * and also if float-->string conversion works as expected.
569  *
570  * @return bool true if problem found
571  */
572 function is_float_problem() {
573     $num1 = 2009010200.01;
574     $num2 = 2009010200.02;
576     return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
579 /**
580  * Try to verify that dataroot is not accessible from web.
581  *
582  * Try to verify that dataroot is not accessible from web.
583  * It is not 100% correct but might help to reduce number of vulnerable sites.
584  * Protection from httpd.conf and .htaccess is not detected properly.
585  *
586  * @uses INSECURE_DATAROOT_WARNING
587  * @uses INSECURE_DATAROOT_ERROR
588  * @param bool $fetchtest try to test public access by fetching file, default false
589  * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
590  */
591 function is_dataroot_insecure($fetchtest=false) {
592     global $CFG;
594     $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
596     $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
597     $rp = strrev(trim($rp, '/'));
598     $rp = explode('/', $rp);
599     foreach($rp as $r) {
600         if (strpos($siteroot, '/'.$r.'/') === 0) {
601             $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
602         } else {
603             break; // probably alias root
604         }
605     }
607     $siteroot = strrev($siteroot);
608     $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
610     if (strpos($dataroot, $siteroot) !== 0) {
611         return false;
612     }
614     if (!$fetchtest) {
615         return INSECURE_DATAROOT_WARNING;
616     }
618     // now try all methods to fetch a test file using http protocol
620     $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
621     preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
622     $httpdocroot = $matches[1];
623     $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
624     make_upload_directory('diag');
625     $testfile = $CFG->dataroot.'/diag/public.txt';
626     if (!file_exists($testfile)) {
627         file_put_contents($testfile, 'test file, do not delete');
628     }
629     $teststr = trim(file_get_contents($testfile));
630     if (empty($teststr)) {
631     // hmm, strange
632         return INSECURE_DATAROOT_WARNING;
633     }
635     $testurl = $datarooturl.'/diag/public.txt';
636     if (extension_loaded('curl') and
637         !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
638         !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
639         ($ch = @curl_init($testurl)) !== false) {
640         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
641         curl_setopt($ch, CURLOPT_HEADER, false);
642         $data = curl_exec($ch);
643         if (!curl_errno($ch)) {
644             $data = trim($data);
645             if ($data === $teststr) {
646                 curl_close($ch);
647                 return INSECURE_DATAROOT_ERROR;
648             }
649         }
650         curl_close($ch);
651     }
653     if ($data = @file_get_contents($testurl)) {
654         $data = trim($data);
655         if ($data === $teststr) {
656             return INSECURE_DATAROOT_ERROR;
657         }
658     }
660     preg_match('|https?://([^/]+)|i', $testurl, $matches);
661     $sitename = $matches[1];
662     $error = 0;
663     if ($fp = @fsockopen($sitename, 80, $error)) {
664         preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
665         $localurl = $matches[1];
666         $out = "GET $localurl HTTP/1.1\r\n";
667         $out .= "Host: $sitename\r\n";
668         $out .= "Connection: Close\r\n\r\n";
669         fwrite($fp, $out);
670         $data = '';
671         $incoming = false;
672         while (!feof($fp)) {
673             if ($incoming) {
674                 $data .= fgets($fp, 1024);
675             } else if (@fgets($fp, 1024) === "\r\n") {
676                     $incoming = true;
677                 }
678         }
679         fclose($fp);
680         $data = trim($data);
681         if ($data === $teststr) {
682             return INSECURE_DATAROOT_ERROR;
683         }
684     }
686     return INSECURE_DATAROOT_WARNING;
689 /**
690  * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
691  */
692 function enable_cli_maintenance_mode() {
693     global $CFG;
695     if (file_exists("$CFG->dataroot/climaintenance.html")) {
696         unlink("$CFG->dataroot/climaintenance.html");
697     }
699     if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
700         $data = $CFG->maintenance_message;
701         $data = bootstrap_renderer::early_error_content($data, null, null, null);
702         $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
704     } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
705         $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
707     } else {
708         $data = get_string('sitemaintenance', 'admin');
709         $data = bootstrap_renderer::early_error_content($data, null, null, null);
710         $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
711     }
713     file_put_contents("$CFG->dataroot/climaintenance.html", $data);
714     chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
717 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
720 /**
721  * Interface for anything appearing in the admin tree
722  *
723  * The interface that is implemented by anything that appears in the admin tree
724  * block. It forces inheriting classes to define a method for checking user permissions
725  * and methods for finding something in the admin tree.
726  *
727  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
728  */
729 interface part_of_admin_tree {
731 /**
732  * Finds a named part_of_admin_tree.
733  *
734  * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
735  * and not parentable_part_of_admin_tree, then this function should only check if
736  * $this->name matches $name. If it does, it should return a reference to $this,
737  * otherwise, it should return a reference to NULL.
738  *
739  * If a class inherits parentable_part_of_admin_tree, this method should be called
740  * recursively on all child objects (assuming, of course, the parent object's name
741  * doesn't match the search criterion).
742  *
743  * @param string $name The internal name of the part_of_admin_tree we're searching for.
744  * @return mixed An object reference or a NULL reference.
745  */
746     public function locate($name);
748     /**
749      * Removes named part_of_admin_tree.
750      *
751      * @param string $name The internal name of the part_of_admin_tree we want to remove.
752      * @return bool success.
753      */
754     public function prune($name);
756     /**
757      * Search using query
758      * @param string $query
759      * @return mixed array-object structure of found settings and pages
760      */
761     public function search($query);
763     /**
764      * Verifies current user's access to this part_of_admin_tree.
765      *
766      * Used to check if the current user has access to this part of the admin tree or
767      * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
768      * then this method is usually just a call to has_capability() in the site context.
769      *
770      * If a class inherits parentable_part_of_admin_tree, this method should return the
771      * logical OR of the return of check_access() on all child objects.
772      *
773      * @return bool True if the user has access, false if she doesn't.
774      */
775     public function check_access();
777     /**
778      * Mostly useful for removing of some parts of the tree in admin tree block.
779      *
780      * @return True is hidden from normal list view
781      */
782     public function is_hidden();
784     /**
785      * Show we display Save button at the page bottom?
786      * @return bool
787      */
788     public function show_save();
792 /**
793  * Interface implemented by any part_of_admin_tree that has children.
794  *
795  * The interface implemented by any part_of_admin_tree that can be a parent
796  * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
797  * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
798  * include an add method for adding other part_of_admin_tree objects as children.
799  *
800  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
801  */
802 interface parentable_part_of_admin_tree extends part_of_admin_tree {
804 /**
805  * Adds a part_of_admin_tree object to the admin tree.
806  *
807  * Used to add a part_of_admin_tree object to this object or a child of this
808  * object. $something should only be added if $destinationname matches
809  * $this->name. If it doesn't, add should be called on child objects that are
810  * also parentable_part_of_admin_tree's.
811  *
812  * $something should be appended as the last child in the $destinationname. If the
813  * $beforesibling is specified, $something should be prepended to it. If the given
814  * sibling is not found, $something should be appended to the end of $destinationname
815  * and a developer debugging message should be displayed.
816  *
817  * @param string $destinationname The internal name of the new parent for $something.
818  * @param part_of_admin_tree $something The object to be added.
819  * @return bool True on success, false on failure.
820  */
821     public function add($destinationname, $something, $beforesibling = null);
826 /**
827  * The object used to represent folders (a.k.a. categories) in the admin tree block.
828  *
829  * Each admin_category object contains a number of part_of_admin_tree objects.
830  *
831  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
832  */
833 class admin_category implements parentable_part_of_admin_tree {
835     /** @var mixed An array of part_of_admin_tree objects that are this object's children */
836     public $children;
837     /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
838     public $name;
839     /** @var string The displayed name for this category. Usually obtained through get_string() */
840     public $visiblename;
841     /** @var bool Should this category be hidden in admin tree block? */
842     public $hidden;
843     /** @var mixed Either a string or an array or strings */
844     public $path;
845     /** @var mixed Either a string or an array or strings */
846     public $visiblepath;
848     /** @var array fast lookup category cache, all categories of one tree point to one cache */
849     protected $category_cache;
851     /**
852      * Constructor for an empty admin category
853      *
854      * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
855      * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
856      * @param bool $hidden hide category in admin tree block, defaults to false
857      */
858     public function __construct($name, $visiblename, $hidden=false) {
859         $this->children    = array();
860         $this->name        = $name;
861         $this->visiblename = $visiblename;
862         $this->hidden      = $hidden;
863     }
865     /**
866      * Returns a reference to the part_of_admin_tree object with internal name $name.
867      *
868      * @param string $name The internal name of the object we want.
869      * @param bool $findpath initialize path and visiblepath arrays
870      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
871      *                  defaults to false
872      */
873     public function locate($name, $findpath=false) {
874         if (is_array($this->category_cache) and !isset($this->category_cache[$this->name])) {
875             // somebody much have purged the cache
876             $this->category_cache[$this->name] = $this;
877         }
879         if ($this->name == $name) {
880             if ($findpath) {
881                 $this->visiblepath[] = $this->visiblename;
882                 $this->path[]        = $this->name;
883             }
884             return $this;
885         }
887         // quick category lookup
888         if (!$findpath and is_array($this->category_cache) and isset($this->category_cache[$name])) {
889             return $this->category_cache[$name];
890         }
892         $return = NULL;
893         foreach($this->children as $childid=>$unused) {
894             if ($return = $this->children[$childid]->locate($name, $findpath)) {
895                 break;
896             }
897         }
899         if (!is_null($return) and $findpath) {
900             $return->visiblepath[] = $this->visiblename;
901             $return->path[]        = $this->name;
902         }
904         return $return;
905     }
907     /**
908      * Search using query
909      *
910      * @param string query
911      * @return mixed array-object structure of found settings and pages
912      */
913     public function search($query) {
914         $result = array();
915         foreach ($this->children as $child) {
916             $subsearch = $child->search($query);
917             if (!is_array($subsearch)) {
918                 debugging('Incorrect search result from '.$child->name);
919                 continue;
920             }
921             $result = array_merge($result, $subsearch);
922         }
923         return $result;
924     }
926     /**
927      * Removes part_of_admin_tree object with internal name $name.
928      *
929      * @param string $name The internal name of the object we want to remove.
930      * @return bool success
931      */
932     public function prune($name) {
934         if ($this->name == $name) {
935             return false;  //can not remove itself
936         }
938         foreach($this->children as $precedence => $child) {
939             if ($child->name == $name) {
940                 // clear cache and delete self
941                 if (is_array($this->category_cache)) {
942                     while($this->category_cache) {
943                         // delete the cache, but keep the original array address
944                         array_pop($this->category_cache);
945                     }
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         $parent = $this->locate($parentname);
972         if (is_null($parent)) {
973             debugging('parent does not exist!');
974             return false;
975         }
977         if ($something instanceof part_of_admin_tree) {
978             if (!($parent instanceof parentable_part_of_admin_tree)) {
979                 debugging('error - parts of tree can be inserted only into parentable parts');
980                 return false;
981             }
982             if (debugging('', DEBUG_DEVELOPER) && !is_null($this->locate($something->name))) {
983                 // The name of the node is already used, simply warn the developer that this should not happen.
984                 // It is intentional to check for the debug level before performing the check.
985                 debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
986             }
987             if (is_null($beforesibling)) {
988                 // Append $something as the parent's last child.
989                 $parent->children[] = $something;
990             } else {
991                 if (!is_string($beforesibling) or trim($beforesibling) === '') {
992                     throw new coding_exception('Unexpected value of the beforesibling parameter');
993                 }
994                 // Try to find the position of the sibling.
995                 $siblingposition = null;
996                 foreach ($parent->children as $childposition => $child) {
997                     if ($child->name === $beforesibling) {
998                         $siblingposition = $childposition;
999                         break;
1000                     }
1001                 }
1002                 if (is_null($siblingposition)) {
1003                     debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
1004                     $parent->children[] = $something;
1005                 } else {
1006                     $parent->children = array_merge(
1007                         array_slice($parent->children, 0, $siblingposition),
1008                         array($something),
1009                         array_slice($parent->children, $siblingposition)
1010                     );
1011                 }
1012             }
1013             if (is_array($this->category_cache) and ($something instanceof admin_category)) {
1014                 if (isset($this->category_cache[$something->name])) {
1015                     debugging('Duplicate admin category name: '.$something->name);
1016                 } else {
1017                     $this->category_cache[$something->name] = $something;
1018                     $something->category_cache =& $this->category_cache;
1019                     foreach ($something->children as $child) {
1020                         // just in case somebody already added subcategories
1021                         if ($child instanceof admin_category) {
1022                             if (isset($this->category_cache[$child->name])) {
1023                                 debugging('Duplicate admin category name: '.$child->name);
1024                             } else {
1025                                 $this->category_cache[$child->name] = $child;
1026                                 $child->category_cache =& $this->category_cache;
1027                             }
1028                         }
1029                     }
1030                 }
1031             }
1032             return true;
1034         } else {
1035             debugging('error - can not add this element');
1036             return false;
1037         }
1039     }
1041     /**
1042      * Checks if the user has access to anything in this category.
1043      *
1044      * @return bool True if the user has access to at least one child in this category, false otherwise.
1045      */
1046     public function check_access() {
1047         foreach ($this->children as $child) {
1048             if ($child->check_access()) {
1049                 return true;
1050             }
1051         }
1052         return false;
1053     }
1055     /**
1056      * Is this category hidden in admin tree block?
1057      *
1058      * @return bool True if hidden
1059      */
1060     public function is_hidden() {
1061         return $this->hidden;
1062     }
1064     /**
1065      * Show we display Save button at the page bottom?
1066      * @return bool
1067      */
1068     public function show_save() {
1069         foreach ($this->children as $child) {
1070             if ($child->show_save()) {
1071                 return true;
1072             }
1073         }
1074         return false;
1075     }
1079 /**
1080  * Root of admin settings tree, does not have any parent.
1081  *
1082  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1083  */
1084 class admin_root extends admin_category {
1085 /** @var array List of errors */
1086     public $errors;
1087     /** @var string search query */
1088     public $search;
1089     /** @var bool full tree flag - true means all settings required, false only pages required */
1090     public $fulltree;
1091     /** @var bool flag indicating loaded tree */
1092     public $loaded;
1093     /** @var mixed site custom defaults overriding defaults in settings files*/
1094     public $custom_defaults;
1096     /**
1097      * @param bool $fulltree true means all settings required,
1098      *                            false only pages required
1099      */
1100     public function __construct($fulltree) {
1101         global $CFG;
1103         parent::__construct('root', get_string('administration'), false);
1104         $this->errors   = array();
1105         $this->search   = '';
1106         $this->fulltree = $fulltree;
1107         $this->loaded   = false;
1109         $this->category_cache = array();
1111         // load custom defaults if found
1112         $this->custom_defaults = null;
1113         $defaultsfile = "$CFG->dirroot/local/defaults.php";
1114         if (is_readable($defaultsfile)) {
1115             $defaults = array();
1116             include($defaultsfile);
1117             if (is_array($defaults) and count($defaults)) {
1118                 $this->custom_defaults = $defaults;
1119             }
1120         }
1121     }
1123     /**
1124      * Empties children array, and sets loaded to false
1125      *
1126      * @param bool $requirefulltree
1127      */
1128     public function purge_children($requirefulltree) {
1129         $this->children = array();
1130         $this->fulltree = ($requirefulltree || $this->fulltree);
1131         $this->loaded   = false;
1132         //break circular dependencies - this helps PHP 5.2
1133         while($this->category_cache) {
1134             array_pop($this->category_cache);
1135         }
1136         $this->category_cache = array();
1137     }
1141 /**
1142  * Links external PHP pages into the admin tree.
1143  *
1144  * See detailed usage example at the top of this document (adminlib.php)
1145  *
1146  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1147  */
1148 class admin_externalpage implements part_of_admin_tree {
1150     /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1151     public $name;
1153     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1154     public $visiblename;
1156     /** @var string The external URL that we should link to when someone requests this external page. */
1157     public $url;
1159     /** @var string The role capability/permission a user must have to access this external page. */
1160     public $req_capability;
1162     /** @var object The context in which capability/permission should be checked, default is site context. */
1163     public $context;
1165     /** @var bool hidden in admin tree block. */
1166     public $hidden;
1168     /** @var mixed either string or array of string */
1169     public $path;
1171     /** @var array list of visible names of page parents */
1172     public $visiblepath;
1174     /**
1175      * Constructor for adding an external page into the admin tree.
1176      *
1177      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1178      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1179      * @param string $url The external URL that we should link to when someone requests this external page.
1180      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1181      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1182      * @param stdClass $context The context the page relates to. Not sure what happens
1183      *      if you specify something other than system or front page. Defaults to system.
1184      */
1185     public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1186         $this->name        = $name;
1187         $this->visiblename = $visiblename;
1188         $this->url         = $url;
1189         if (is_array($req_capability)) {
1190             $this->req_capability = $req_capability;
1191         } else {
1192             $this->req_capability = array($req_capability);
1193         }
1194         $this->hidden = $hidden;
1195         $this->context = $context;
1196     }
1198     /**
1199      * Returns a reference to the part_of_admin_tree object with internal name $name.
1200      *
1201      * @param string $name The internal name of the object we want.
1202      * @param bool $findpath defaults to false
1203      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1204      */
1205     public function locate($name, $findpath=false) {
1206         if ($this->name == $name) {
1207             if ($findpath) {
1208                 $this->visiblepath = array($this->visiblename);
1209                 $this->path        = array($this->name);
1210             }
1211             return $this;
1212         } else {
1213             $return = NULL;
1214             return $return;
1215         }
1216     }
1218     /**
1219      * This function always returns false, required function by interface
1220      *
1221      * @param string $name
1222      * @return false
1223      */
1224     public function prune($name) {
1225         return false;
1226     }
1228     /**
1229      * Search using query
1230      *
1231      * @param string $query
1232      * @return mixed array-object structure of found settings and pages
1233      */
1234     public function search($query) {
1235         $found = false;
1236         if (strpos(strtolower($this->name), $query) !== false) {
1237             $found = true;
1238         } else if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1239                 $found = true;
1240             }
1241         if ($found) {
1242             $result = new stdClass();
1243             $result->page     = $this;
1244             $result->settings = array();
1245             return array($this->name => $result);
1246         } else {
1247             return array();
1248         }
1249     }
1251     /**
1252      * Determines if the current user has access to this external page based on $this->req_capability.
1253      *
1254      * @return bool True if user has access, false otherwise.
1255      */
1256     public function check_access() {
1257         global $CFG;
1258         $context = empty($this->context) ? context_system::instance() : $this->context;
1259         foreach($this->req_capability as $cap) {
1260             if (has_capability($cap, $context)) {
1261                 return true;
1262             }
1263         }
1264         return false;
1265     }
1267     /**
1268      * Is this external page hidden in admin tree block?
1269      *
1270      * @return bool True if hidden
1271      */
1272     public function is_hidden() {
1273         return $this->hidden;
1274     }
1276     /**
1277      * Show we display Save button at the page bottom?
1278      * @return bool
1279      */
1280     public function show_save() {
1281         return false;
1282     }
1286 /**
1287  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1288  *
1289  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1290  */
1291 class admin_settingpage implements part_of_admin_tree {
1293     /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1294     public $name;
1296     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1297     public $visiblename;
1299     /** @var mixed An array of admin_setting objects that are part of this setting page. */
1300     public $settings;
1302     /** @var string The role capability/permission a user must have to access this external page. */
1303     public $req_capability;
1305     /** @var object The context in which capability/permission should be checked, default is site context. */
1306     public $context;
1308     /** @var bool hidden in admin tree block. */
1309     public $hidden;
1311     /** @var mixed string of paths or array of strings of paths */
1312     public $path;
1314     /** @var array list of visible names of page parents */
1315     public $visiblepath;
1317     /**
1318      * see admin_settingpage for details of this function
1319      *
1320      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1321      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1322      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1323      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1324      * @param stdClass $context The context the page relates to. Not sure what happens
1325      *      if you specify something other than system or front page. Defaults to system.
1326      */
1327     public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1328         $this->settings    = new stdClass();
1329         $this->name        = $name;
1330         $this->visiblename = $visiblename;
1331         if (is_array($req_capability)) {
1332             $this->req_capability = $req_capability;
1333         } else {
1334             $this->req_capability = array($req_capability);
1335         }
1336         $this->hidden      = $hidden;
1337         $this->context     = $context;
1338     }
1340     /**
1341      * see admin_category
1342      *
1343      * @param string $name
1344      * @param bool $findpath
1345      * @return mixed Object (this) if name ==  this->name, else returns null
1346      */
1347     public function locate($name, $findpath=false) {
1348         if ($this->name == $name) {
1349             if ($findpath) {
1350                 $this->visiblepath = array($this->visiblename);
1351                 $this->path        = array($this->name);
1352             }
1353             return $this;
1354         } else {
1355             $return = NULL;
1356             return $return;
1357         }
1358     }
1360     /**
1361      * Search string in settings page.
1362      *
1363      * @param string $query
1364      * @return array
1365      */
1366     public function search($query) {
1367         $found = array();
1369         foreach ($this->settings as $setting) {
1370             if ($setting->is_related($query)) {
1371                 $found[] = $setting;
1372             }
1373         }
1375         if ($found) {
1376             $result = new stdClass();
1377             $result->page     = $this;
1378             $result->settings = $found;
1379             return array($this->name => $result);
1380         }
1382         $found = false;
1383         if (strpos(strtolower($this->name), $query) !== false) {
1384             $found = true;
1385         } else if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1386                 $found = true;
1387             }
1388         if ($found) {
1389             $result = new stdClass();
1390             $result->page     = $this;
1391             $result->settings = array();
1392             return array($this->name => $result);
1393         } else {
1394             return array();
1395         }
1396     }
1398     /**
1399      * This function always returns false, required by interface
1400      *
1401      * @param string $name
1402      * @return bool Always false
1403      */
1404     public function prune($name) {
1405         return false;
1406     }
1408     /**
1409      * adds an admin_setting to this admin_settingpage
1410      *
1411      * 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
1412      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1413      *
1414      * @param object $setting is the admin_setting object you want to add
1415      * @return bool true if successful, false if not
1416      */
1417     public function add($setting) {
1418         if (!($setting instanceof admin_setting)) {
1419             debugging('error - not a setting instance');
1420             return false;
1421         }
1423         $this->settings->{$setting->name} = $setting;
1424         return true;
1425     }
1427     /**
1428      * see admin_externalpage
1429      *
1430      * @return bool Returns true for yes false for no
1431      */
1432     public function check_access() {
1433         global $CFG;
1434         $context = empty($this->context) ? context_system::instance() : $this->context;
1435         foreach($this->req_capability as $cap) {
1436             if (has_capability($cap, $context)) {
1437                 return true;
1438             }
1439         }
1440         return false;
1441     }
1443     /**
1444      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1445      * @return string Returns an XHTML string
1446      */
1447     public function output_html() {
1448         $adminroot = admin_get_root();
1449         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1450         foreach($this->settings as $setting) {
1451             $fullname = $setting->get_full_name();
1452             if (array_key_exists($fullname, $adminroot->errors)) {
1453                 $data = $adminroot->errors[$fullname]->data;
1454             } else {
1455                 $data = $setting->get_setting();
1456                 // do not use defaults if settings not available - upgrade settings handles the defaults!
1457             }
1458             $return .= $setting->output_html($data);
1459         }
1460         $return .= '</fieldset>';
1461         return $return;
1462     }
1464     /**
1465      * Is this settings page hidden in admin tree block?
1466      *
1467      * @return bool True if hidden
1468      */
1469     public function is_hidden() {
1470         return $this->hidden;
1471     }
1473     /**
1474      * Show we display Save button at the page bottom?
1475      * @return bool
1476      */
1477     public function show_save() {
1478         foreach($this->settings as $setting) {
1479             if (empty($setting->nosave)) {
1480                 return true;
1481             }
1482         }
1483         return false;
1484     }
1488 /**
1489  * Admin settings class. Only exists on setting pages.
1490  * Read & write happens at this level; no authentication.
1491  *
1492  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1493  */
1494 abstract class admin_setting {
1495     /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1496     public $name;
1497     /** @var string localised name */
1498     public $visiblename;
1499     /** @var string localised long description in Markdown format */
1500     public $description;
1501     /** @var mixed Can be string or array of string */
1502     public $defaultsetting;
1503     /** @var string */
1504     public $updatedcallback;
1505     /** @var mixed can be String or Null.  Null means main config table */
1506     public $plugin; // null means main config table
1507     /** @var bool true indicates this setting does not actually save anything, just information */
1508     public $nosave = false;
1509     /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1510     public $affectsmodinfo = false;
1512     /**
1513      * Constructor
1514      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1515      *                     or 'myplugin/mysetting' for ones in config_plugins.
1516      * @param string $visiblename localised name
1517      * @param string $description localised long description
1518      * @param mixed $defaultsetting string or array depending on implementation
1519      */
1520     public function __construct($name, $visiblename, $description, $defaultsetting) {
1521         $this->parse_setting_name($name);
1522         $this->visiblename    = $visiblename;
1523         $this->description    = $description;
1524         $this->defaultsetting = $defaultsetting;
1525     }
1527     /**
1528      * Set up $this->name and potentially $this->plugin
1529      *
1530      * Set up $this->name and possibly $this->plugin based on whether $name looks
1531      * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1532      * on the names, that is, output a developer debug warning if the name
1533      * contains anything other than [a-zA-Z0-9_]+.
1534      *
1535      * @param string $name the setting name passed in to the constructor.
1536      */
1537     private function parse_setting_name($name) {
1538         $bits = explode('/', $name);
1539         if (count($bits) > 2) {
1540             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1541         }
1542         $this->name = array_pop($bits);
1543         if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1544             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1545         }
1546         if (!empty($bits)) {
1547             $this->plugin = array_pop($bits);
1548             if ($this->plugin === 'moodle') {
1549                 $this->plugin = null;
1550             } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1551                     throw new moodle_exception('invalidadminsettingname', '', '', $name);
1552                 }
1553         }
1554     }
1556     /**
1557      * Returns the fullname prefixed by the plugin
1558      * @return string
1559      */
1560     public function get_full_name() {
1561         return 's_'.$this->plugin.'_'.$this->name;
1562     }
1564     /**
1565      * Returns the ID string based on plugin and name
1566      * @return string
1567      */
1568     public function get_id() {
1569         return 'id_s_'.$this->plugin.'_'.$this->name;
1570     }
1572     /**
1573      * @param bool $affectsmodinfo If true, changes to this setting will
1574      *   cause the course cache to be rebuilt
1575      */
1576     public function set_affects_modinfo($affectsmodinfo) {
1577         $this->affectsmodinfo = $affectsmodinfo;
1578     }
1580     /**
1581      * Returns the config if possible
1582      *
1583      * @return mixed returns config if successful else null
1584      */
1585     public function config_read($name) {
1586         global $CFG;
1587         if (!empty($this->plugin)) {
1588             $value = get_config($this->plugin, $name);
1589             return $value === false ? NULL : $value;
1591         } else {
1592             if (isset($CFG->$name)) {
1593                 return $CFG->$name;
1594             } else {
1595                 return NULL;
1596             }
1597         }
1598     }
1600     /**
1601      * Used to set a config pair and log change
1602      *
1603      * @param string $name
1604      * @param mixed $value Gets converted to string if not null
1605      * @return bool Write setting to config table
1606      */
1607     public function config_write($name, $value) {
1608         global $DB, $USER, $CFG;
1610         if ($this->nosave) {
1611             return true;
1612         }
1614         // make sure it is a real change
1615         $oldvalue = get_config($this->plugin, $name);
1616         $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1617         $value = is_null($value) ? null : (string)$value;
1619         if ($oldvalue === $value) {
1620             return true;
1621         }
1623         // store change
1624         set_config($name, $value, $this->plugin);
1626         // Some admin settings affect course modinfo
1627         if ($this->affectsmodinfo) {
1628             // Clear course cache for all courses
1629             rebuild_course_cache(0, true);
1630         }
1632         // log change
1633         $log = new stdClass();
1634         $log->userid       = during_initial_install() ? 0 :$USER->id; // 0 as user id during install
1635         $log->timemodified = time();
1636         $log->plugin       = $this->plugin;
1637         $log->name         = $name;
1638         $log->value        = $value;
1639         $log->oldvalue     = $oldvalue;
1640         $DB->insert_record('config_log', $log);
1642         return true; // BC only
1643     }
1645     /**
1646      * Returns current value of this setting
1647      * @return mixed array or string depending on instance, NULL means not set yet
1648      */
1649     public abstract function get_setting();
1651     /**
1652      * Returns default setting if exists
1653      * @return mixed array or string depending on instance; NULL means no default, user must supply
1654      */
1655     public function get_defaultsetting() {
1656         $adminroot =  admin_get_root(false, false);
1657         if (!empty($adminroot->custom_defaults)) {
1658             $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1659             if (isset($adminroot->custom_defaults[$plugin])) {
1660                 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
1661                     return $adminroot->custom_defaults[$plugin][$this->name];
1662                 }
1663             }
1664         }
1665         return $this->defaultsetting;
1666     }
1668     /**
1669      * Store new setting
1670      *
1671      * @param mixed $data string or array, must not be NULL
1672      * @return string empty string if ok, string error message otherwise
1673      */
1674     public abstract function write_setting($data);
1676     /**
1677      * Return part of form with setting
1678      * This function should always be overwritten
1679      *
1680      * @param mixed $data array or string depending on setting
1681      * @param string $query
1682      * @return string
1683      */
1684     public function output_html($data, $query='') {
1685     // should be overridden
1686         return;
1687     }
1689     /**
1690      * Function called if setting updated - cleanup, cache reset, etc.
1691      * @param string $functionname Sets the function name
1692      * @return void
1693      */
1694     public function set_updatedcallback($functionname) {
1695         $this->updatedcallback = $functionname;
1696     }
1698     /**
1699      * Is setting related to query text - used when searching
1700      * @param string $query
1701      * @return bool
1702      */
1703     public function is_related($query) {
1704         if (strpos(strtolower($this->name), $query) !== false) {
1705             return true;
1706         }
1707         if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1708             return true;
1709         }
1710         if (strpos(textlib::strtolower($this->description), $query) !== false) {
1711             return true;
1712         }
1713         $current = $this->get_setting();
1714         if (!is_null($current)) {
1715             if (is_string($current)) {
1716                 if (strpos(textlib::strtolower($current), $query) !== false) {
1717                     return true;
1718                 }
1719             }
1720         }
1721         $default = $this->get_defaultsetting();
1722         if (!is_null($default)) {
1723             if (is_string($default)) {
1724                 if (strpos(textlib::strtolower($default), $query) !== false) {
1725                     return true;
1726                 }
1727             }
1728         }
1729         return false;
1730     }
1734 /**
1735  * No setting - just heading and text.
1736  *
1737  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1738  */
1739 class admin_setting_heading extends admin_setting {
1741     /**
1742      * not a setting, just text
1743      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1744      * @param string $heading heading
1745      * @param string $information text in box
1746      */
1747     public function __construct($name, $heading, $information) {
1748         $this->nosave = true;
1749         parent::__construct($name, $heading, $information, '');
1750     }
1752     /**
1753      * Always returns true
1754      * @return bool Always returns true
1755      */
1756     public function get_setting() {
1757         return true;
1758     }
1760     /**
1761      * Always returns true
1762      * @return bool Always returns true
1763      */
1764     public function get_defaultsetting() {
1765         return true;
1766     }
1768     /**
1769      * Never write settings
1770      * @return string Always returns an empty string
1771      */
1772     public function write_setting($data) {
1773     // do not write any setting
1774         return '';
1775     }
1777     /**
1778      * Returns an HTML string
1779      * @return string Returns an HTML string
1780      */
1781     public function output_html($data, $query='') {
1782         global $OUTPUT;
1783         $return = '';
1784         if ($this->visiblename != '') {
1785             $return .= $OUTPUT->heading($this->visiblename, 3, 'main');
1786         }
1787         if ($this->description != '') {
1788             $return .= $OUTPUT->box(highlight($query, markdown_to_html($this->description)), 'generalbox formsettingheading');
1789         }
1790         return $return;
1791     }
1795 /**
1796  * The most flexibly setting, user is typing text
1797  *
1798  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1799  */
1800 class admin_setting_configtext extends admin_setting {
1802     /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
1803     public $paramtype;
1804     /** @var int default field size */
1805     public $size;
1807     /**
1808      * Config text constructor
1809      *
1810      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1811      * @param string $visiblename localised
1812      * @param string $description long localised info
1813      * @param string $defaultsetting
1814      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
1815      * @param int $size default field size
1816      */
1817     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
1818         $this->paramtype = $paramtype;
1819         if (!is_null($size)) {
1820             $this->size  = $size;
1821         } else {
1822             $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
1823         }
1824         parent::__construct($name, $visiblename, $description, $defaultsetting);
1825     }
1827     /**
1828      * Return the setting
1829      *
1830      * @return mixed returns config if successful else null
1831      */
1832     public function get_setting() {
1833         return $this->config_read($this->name);
1834     }
1836     public function write_setting($data) {
1837         if ($this->paramtype === PARAM_INT and $data === '') {
1838         // do not complain if '' used instead of 0
1839             $data = 0;
1840         }
1841         // $data is a string
1842         $validated = $this->validate($data);
1843         if ($validated !== true) {
1844             return $validated;
1845         }
1846         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1847     }
1849     /**
1850      * Validate data before storage
1851      * @param string data
1852      * @return mixed true if ok string if error found
1853      */
1854     public function validate($data) {
1855         // allow paramtype to be a custom regex if it is the form of /pattern/
1856         if (preg_match('#^/.*/$#', $this->paramtype)) {
1857             if (preg_match($this->paramtype, $data)) {
1858                 return true;
1859             } else {
1860                 return get_string('validateerror', 'admin');
1861             }
1863         } else if ($this->paramtype === PARAM_RAW) {
1864             return true;
1866         } else {
1867             $cleaned = clean_param($data, $this->paramtype);
1868             if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
1869                 return true;
1870             } else {
1871                 return get_string('validateerror', 'admin');
1872             }
1873         }
1874     }
1876     /**
1877      * Return an XHTML string for the setting
1878      * @return string Returns an XHTML string
1879      */
1880     public function output_html($data, $query='') {
1881         $default = $this->get_defaultsetting();
1883         return format_admin_setting($this, $this->visiblename,
1884         '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
1885         $this->description, true, '', $default, $query);
1886     }
1890 /**
1891  * General text area without html editor.
1892  *
1893  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1894  */
1895 class admin_setting_configtextarea extends admin_setting_configtext {
1896     private $rows;
1897     private $cols;
1899     /**
1900      * @param string $name
1901      * @param string $visiblename
1902      * @param string $description
1903      * @param mixed $defaultsetting string or array
1904      * @param mixed $paramtype
1905      * @param string $cols The number of columns to make the editor
1906      * @param string $rows The number of rows to make the editor
1907      */
1908     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1909         $this->rows = $rows;
1910         $this->cols = $cols;
1911         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1912     }
1914     /**
1915      * Returns an XHTML string for the editor
1916      *
1917      * @param string $data
1918      * @param string $query
1919      * @return string XHTML string for the editor
1920      */
1921     public function output_html($data, $query='') {
1922         $default = $this->get_defaultsetting();
1924         $defaultinfo = $default;
1925         if (!is_null($default) and $default !== '') {
1926             $defaultinfo = "\n".$default;
1927         }
1929         return format_admin_setting($this, $this->visiblename,
1930         '<div class="form-textarea" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1931         $this->description, true, '', $defaultinfo, $query);
1932     }
1936 /**
1937  * General text area with html editor.
1938  */
1939 class admin_setting_confightmleditor extends admin_setting_configtext {
1940     private $rows;
1941     private $cols;
1943     /**
1944      * @param string $name
1945      * @param string $visiblename
1946      * @param string $description
1947      * @param mixed $defaultsetting string or array
1948      * @param mixed $paramtype
1949      */
1950     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1951         $this->rows = $rows;
1952         $this->cols = $cols;
1953         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1954         editors_head_setup();
1955     }
1957     /**
1958      * Returns an XHTML string for the editor
1959      *
1960      * @param string $data
1961      * @param string $query
1962      * @return string XHTML string for the editor
1963      */
1964     public function output_html($data, $query='') {
1965         $default = $this->get_defaultsetting();
1967         $defaultinfo = $default;
1968         if (!is_null($default) and $default !== '') {
1969             $defaultinfo = "\n".$default;
1970         }
1972         $editor = editors_get_preferred_editor(FORMAT_HTML);
1973         $editor->use_editor($this->get_id(), array('noclean'=>true));
1975         return format_admin_setting($this, $this->visiblename,
1976         '<div class="form-textarea"><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1977         $this->description, true, '', $defaultinfo, $query);
1978     }
1982 /**
1983  * Password field, allows unmasking of password
1984  *
1985  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1986  */
1987 class admin_setting_configpasswordunmask extends admin_setting_configtext {
1988     /**
1989      * Constructor
1990      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1991      * @param string $visiblename localised
1992      * @param string $description long localised info
1993      * @param string $defaultsetting default password
1994      */
1995     public function __construct($name, $visiblename, $description, $defaultsetting) {
1996         parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
1997     }
1999     /**
2000      * Returns XHTML for the field
2001      * Writes Javascript into the HTML below right before the last div
2002      *
2003      * @todo Make javascript available through newer methods if possible
2004      * @param string $data Value for the field
2005      * @param string $query Passed as final argument for format_admin_setting
2006      * @return string XHTML field
2007      */
2008     public function output_html($data, $query='') {
2009         $id = $this->get_id();
2010         $unmask = get_string('unmaskpassword', 'form');
2011         $unmaskjs = '<script type="text/javascript">
2012 //<![CDATA[
2013 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
2015 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
2017 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
2019 var unmaskchb = document.createElement("input");
2020 unmaskchb.setAttribute("type", "checkbox");
2021 unmaskchb.setAttribute("id", "'.$id.'unmask");
2022 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
2023 unmaskdiv.appendChild(unmaskchb);
2025 var unmasklbl = document.createElement("label");
2026 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
2027 if (is_ie) {
2028   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
2029 } else {
2030   unmasklbl.setAttribute("for", "'.$id.'unmask");
2032 unmaskdiv.appendChild(unmasklbl);
2034 if (is_ie) {
2035   // ugly hack to work around the famous onchange IE bug
2036   unmaskchb.onclick = function() {this.blur();};
2037   unmaskdiv.onclick = function() {this.blur();};
2039 //]]>
2040 </script>';
2041         return format_admin_setting($this, $this->visiblename,
2042         '<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>',
2043         $this->description, true, '', NULL, $query);
2044     }
2048 /**
2049  * Path to directory
2050  *
2051  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2052  */
2053 class admin_setting_configfile extends admin_setting_configtext {
2054     /**
2055      * Constructor
2056      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2057      * @param string $visiblename localised
2058      * @param string $description long localised info
2059      * @param string $defaultdirectory default directory location
2060      */
2061     public function __construct($name, $visiblename, $description, $defaultdirectory) {
2062         parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2063     }
2065     /**
2066      * Returns XHTML for the field
2067      *
2068      * Returns XHTML for the field and also checks whether the file
2069      * specified in $data exists using file_exists()
2070      *
2071      * @param string $data File name and path to use in value attr
2072      * @param string $query
2073      * @return string XHTML field
2074      */
2075     public function output_html($data, $query='') {
2076         $default = $this->get_defaultsetting();
2078         if ($data) {
2079             if (file_exists($data)) {
2080                 $executable = '<span class="pathok">&#x2714;</span>';
2081             } else {
2082                 $executable = '<span class="patherror">&#x2718;</span>';
2083             }
2084         } else {
2085             $executable = '';
2086         }
2088         return format_admin_setting($this, $this->visiblename,
2089         '<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>',
2090         $this->description, true, '', $default, $query);
2091     }
2095 /**
2096  * Path to executable file
2097  *
2098  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2099  */
2100 class admin_setting_configexecutable extends admin_setting_configfile {
2102     /**
2103      * Returns an XHTML field
2104      *
2105      * @param string $data This is the value for the field
2106      * @param string $query
2107      * @return string XHTML field
2108      */
2109     public function output_html($data, $query='') {
2110         $default = $this->get_defaultsetting();
2112         if ($data) {
2113             if (file_exists($data) and is_executable($data)) {
2114                 $executable = '<span class="pathok">&#x2714;</span>';
2115             } else {
2116                 $executable = '<span class="patherror">&#x2718;</span>';
2117             }
2118         } else {
2119             $executable = '';
2120         }
2122         return format_admin_setting($this, $this->visiblename,
2123         '<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>',
2124         $this->description, true, '', $default, $query);
2125     }
2129 /**
2130  * Path to directory
2131  *
2132  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2133  */
2134 class admin_setting_configdirectory extends admin_setting_configfile {
2136     /**
2137      * Returns an XHTML field
2138      *
2139      * @param string $data This is the value for the field
2140      * @param string $query
2141      * @return string XHTML
2142      */
2143     public function output_html($data, $query='') {
2144         $default = $this->get_defaultsetting();
2146         if ($data) {
2147             if (file_exists($data) and is_dir($data)) {
2148                 $executable = '<span class="pathok">&#x2714;</span>';
2149             } else {
2150                 $executable = '<span class="patherror">&#x2718;</span>';
2151             }
2152         } else {
2153             $executable = '';
2154         }
2156         return format_admin_setting($this, $this->visiblename,
2157         '<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>',
2158         $this->description, true, '', $default, $query);
2159     }
2163 /**
2164  * Checkbox
2165  *
2166  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2167  */
2168 class admin_setting_configcheckbox extends admin_setting {
2169     /** @var string Value used when checked */
2170     public $yes;
2171     /** @var string Value used when not checked */
2172     public $no;
2174     /**
2175      * Constructor
2176      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2177      * @param string $visiblename localised
2178      * @param string $description long localised info
2179      * @param string $defaultsetting
2180      * @param string $yes value used when checked
2181      * @param string $no value used when not checked
2182      */
2183     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2184         parent::__construct($name, $visiblename, $description, $defaultsetting);
2185         $this->yes = (string)$yes;
2186         $this->no  = (string)$no;
2187     }
2189     /**
2190      * Retrieves the current setting using the objects name
2191      *
2192      * @return string
2193      */
2194     public function get_setting() {
2195         return $this->config_read($this->name);
2196     }
2198     /**
2199      * Sets the value for the setting
2200      *
2201      * Sets the value for the setting to either the yes or no values
2202      * of the object by comparing $data to yes
2203      *
2204      * @param mixed $data Gets converted to str for comparison against yes value
2205      * @return string empty string or error
2206      */
2207     public function write_setting($data) {
2208         if ((string)$data === $this->yes) { // convert to strings before comparison
2209             $data = $this->yes;
2210         } else {
2211             $data = $this->no;
2212         }
2213         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2214     }
2216     /**
2217      * Returns an XHTML checkbox field
2218      *
2219      * @param string $data If $data matches yes then checkbox is checked
2220      * @param string $query
2221      * @return string XHTML field
2222      */
2223     public function output_html($data, $query='') {
2224         $default = $this->get_defaultsetting();
2226         if (!is_null($default)) {
2227             if ((string)$default === $this->yes) {
2228                 $defaultinfo = get_string('checkboxyes', 'admin');
2229             } else {
2230                 $defaultinfo = get_string('checkboxno', 'admin');
2231             }
2232         } else {
2233             $defaultinfo = NULL;
2234         }
2236         if ((string)$data === $this->yes) { // convert to strings before comparison
2237             $checked = 'checked="checked"';
2238         } else {
2239             $checked = '';
2240         }
2242         return format_admin_setting($this, $this->visiblename,
2243         '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2244             .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2245         $this->description, true, '', $defaultinfo, $query);
2246     }
2250 /**
2251  * Multiple checkboxes, each represents different value, stored in csv format
2252  *
2253  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2254  */
2255 class admin_setting_configmulticheckbox extends admin_setting {
2256     /** @var array Array of choices value=>label */
2257     public $choices;
2259     /**
2260      * Constructor: uses parent::__construct
2261      *
2262      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2263      * @param string $visiblename localised
2264      * @param string $description long localised info
2265      * @param array $defaultsetting array of selected
2266      * @param array $choices array of $value=>$label for each checkbox
2267      */
2268     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2269         $this->choices = $choices;
2270         parent::__construct($name, $visiblename, $description, $defaultsetting);
2271     }
2273     /**
2274      * This public function may be used in ancestors for lazy loading of choices
2275      *
2276      * @todo Check if this function is still required content commented out only returns true
2277      * @return bool true if loaded, false if error
2278      */
2279     public function load_choices() {
2280         /*
2281         if (is_array($this->choices)) {
2282             return true;
2283         }
2284         .... load choices here
2285         */
2286         return true;
2287     }
2289     /**
2290      * Is setting related to query text - used when searching
2291      *
2292      * @param string $query
2293      * @return bool true on related, false on not or failure
2294      */
2295     public function is_related($query) {
2296         if (!$this->load_choices() or empty($this->choices)) {
2297             return false;
2298         }
2299         if (parent::is_related($query)) {
2300             return true;
2301         }
2303         foreach ($this->choices as $desc) {
2304             if (strpos(textlib::strtolower($desc), $query) !== false) {
2305                 return true;
2306             }
2307         }
2308         return false;
2309     }
2311     /**
2312      * Returns the current setting if it is set
2313      *
2314      * @return mixed null if null, else an array
2315      */
2316     public function get_setting() {
2317         $result = $this->config_read($this->name);
2319         if (is_null($result)) {
2320             return NULL;
2321         }
2322         if ($result === '') {
2323             return array();
2324         }
2325         $enabled = explode(',', $result);
2326         $setting = array();
2327         foreach ($enabled as $option) {
2328             $setting[$option] = 1;
2329         }
2330         return $setting;
2331     }
2333     /**
2334      * Saves the setting(s) provided in $data
2335      *
2336      * @param array $data An array of data, if not array returns empty str
2337      * @return mixed empty string on useless data or bool true=success, false=failed
2338      */
2339     public function write_setting($data) {
2340         if (!is_array($data)) {
2341             return ''; // ignore it
2342         }
2343         if (!$this->load_choices() or empty($this->choices)) {
2344             return '';
2345         }
2346         unset($data['xxxxx']);
2347         $result = array();
2348         foreach ($data as $key => $value) {
2349             if ($value and array_key_exists($key, $this->choices)) {
2350                 $result[] = $key;
2351             }
2352         }
2353         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2354     }
2356     /**
2357      * Returns XHTML field(s) as required by choices
2358      *
2359      * Relies on data being an array should data ever be another valid vartype with
2360      * acceptable value this may cause a warning/error
2361      * if (!is_array($data)) would fix the problem
2362      *
2363      * @todo Add vartype handling to ensure $data is an array
2364      *
2365      * @param array $data An array of checked values
2366      * @param string $query
2367      * @return string XHTML field
2368      */
2369     public function output_html($data, $query='') {
2370         if (!$this->load_choices() or empty($this->choices)) {
2371             return '';
2372         }
2373         $default = $this->get_defaultsetting();
2374         if (is_null($default)) {
2375             $default = array();
2376         }
2377         if (is_null($data)) {
2378             $data = array();
2379         }
2380         $options = array();
2381         $defaults = array();
2382         foreach ($this->choices as $key=>$description) {
2383             if (!empty($data[$key])) {
2384                 $checked = 'checked="checked"';
2385             } else {
2386                 $checked = '';
2387             }
2388             if (!empty($default[$key])) {
2389                 $defaults[] = $description;
2390             }
2392             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
2393                 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
2394         }
2396         if (is_null($default)) {
2397             $defaultinfo = NULL;
2398         } else if (!empty($defaults)) {
2399                 $defaultinfo = implode(', ', $defaults);
2400             } else {
2401                 $defaultinfo = get_string('none');
2402             }
2404         $return = '<div class="form-multicheckbox">';
2405         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2406         if ($options) {
2407             $return .= '<ul>';
2408             foreach ($options as $option) {
2409                 $return .= '<li>'.$option.'</li>';
2410             }
2411             $return .= '</ul>';
2412         }
2413         $return .= '</div>';
2415         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2417     }
2421 /**
2422  * Multiple checkboxes 2, value stored as string 00101011
2423  *
2424  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2425  */
2426 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
2428     /**
2429      * Returns the setting if set
2430      *
2431      * @return mixed null if not set, else an array of set settings
2432      */
2433     public function get_setting() {
2434         $result = $this->config_read($this->name);
2435         if (is_null($result)) {
2436             return NULL;
2437         }
2438         if (!$this->load_choices()) {
2439             return NULL;
2440         }
2441         $result = str_pad($result, count($this->choices), '0');
2442         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2443         $setting = array();
2444         foreach ($this->choices as $key=>$unused) {
2445             $value = array_shift($result);
2446             if ($value) {
2447                 $setting[$key] = 1;
2448             }
2449         }
2450         return $setting;
2451     }
2453     /**
2454      * Save setting(s) provided in $data param
2455      *
2456      * @param array $data An array of settings to save
2457      * @return mixed empty string for bad data or bool true=>success, false=>error
2458      */
2459     public function write_setting($data) {
2460         if (!is_array($data)) {
2461             return ''; // ignore it
2462         }
2463         if (!$this->load_choices() or empty($this->choices)) {
2464             return '';
2465         }
2466         $result = '';
2467         foreach ($this->choices as $key=>$unused) {
2468             if (!empty($data[$key])) {
2469                 $result .= '1';
2470             } else {
2471                 $result .= '0';
2472             }
2473         }
2474         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2475     }
2479 /**
2480  * Select one value from list
2481  *
2482  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2483  */
2484 class admin_setting_configselect extends admin_setting {
2485     /** @var array Array of choices value=>label */
2486     public $choices;
2488     /**
2489      * Constructor
2490      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2491      * @param string $visiblename localised
2492      * @param string $description long localised info
2493      * @param string|int $defaultsetting
2494      * @param array $choices array of $value=>$label for each selection
2495      */
2496     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2497         $this->choices = $choices;
2498         parent::__construct($name, $visiblename, $description, $defaultsetting);
2499     }
2501     /**
2502      * This function may be used in ancestors for lazy loading of choices
2503      *
2504      * Override this method if loading of choices is expensive, such
2505      * as when it requires multiple db requests.
2506      *
2507      * @return bool true if loaded, false if error
2508      */
2509     public function load_choices() {
2510         /*
2511         if (is_array($this->choices)) {
2512             return true;
2513         }
2514         .... load choices here
2515         */
2516         return true;
2517     }
2519     /**
2520      * Check if this is $query is related to a choice
2521      *
2522      * @param string $query
2523      * @return bool true if related, false if not
2524      */
2525     public function is_related($query) {
2526         if (parent::is_related($query)) {
2527             return true;
2528         }
2529         if (!$this->load_choices()) {
2530             return false;
2531         }
2532         foreach ($this->choices as $key=>$value) {
2533             if (strpos(textlib::strtolower($key), $query) !== false) {
2534                 return true;
2535             }
2536             if (strpos(textlib::strtolower($value), $query) !== false) {
2537                 return true;
2538             }
2539         }
2540         return false;
2541     }
2543     /**
2544      * Return the setting
2545      *
2546      * @return mixed returns config if successful else null
2547      */
2548     public function get_setting() {
2549         return $this->config_read($this->name);
2550     }
2552     /**
2553      * Save a setting
2554      *
2555      * @param string $data
2556      * @return string empty of error string
2557      */
2558     public function write_setting($data) {
2559         if (!$this->load_choices() or empty($this->choices)) {
2560             return '';
2561         }
2562         if (!array_key_exists($data, $this->choices)) {
2563             return ''; // ignore it
2564         }
2566         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2567     }
2569     /**
2570      * Returns XHTML select field
2571      *
2572      * Ensure the options are loaded, and generate the XHTML for the select
2573      * element and any warning message. Separating this out from output_html
2574      * makes it easier to subclass this class.
2575      *
2576      * @param string $data the option to show as selected.
2577      * @param string $current the currently selected option in the database, null if none.
2578      * @param string $default the default selected option.
2579      * @return array the HTML for the select element, and a warning message.
2580      */
2581     public function output_select_html($data, $current, $default, $extraname = '') {
2582         if (!$this->load_choices() or empty($this->choices)) {
2583             return array('', '');
2584         }
2586         $warning = '';
2587         if (is_null($current)) {
2588         // first run
2589         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2590             // no warning
2591             } else if (!array_key_exists($current, $this->choices)) {
2592                     $warning = get_string('warningcurrentsetting', 'admin', s($current));
2593                     if (!is_null($default) and $data == $current) {
2594                         $data = $default; // use default instead of first value when showing the form
2595                     }
2596                 }
2598         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
2599         foreach ($this->choices as $key => $value) {
2600         // the string cast is needed because key may be integer - 0 is equal to most strings!
2601             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
2602         }
2603         $selecthtml .= '</select>';
2604         return array($selecthtml, $warning);
2605     }
2607     /**
2608      * Returns XHTML select field and wrapping div(s)
2609      *
2610      * @see output_select_html()
2611      *
2612      * @param string $data the option to show as selected
2613      * @param string $query
2614      * @return string XHTML field and wrapping div
2615      */
2616     public function output_html($data, $query='') {
2617         $default = $this->get_defaultsetting();
2618         $current = $this->get_setting();
2620         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2621         if (!$selecthtml) {
2622             return '';
2623         }
2625         if (!is_null($default) and array_key_exists($default, $this->choices)) {
2626             $defaultinfo = $this->choices[$default];
2627         } else {
2628             $defaultinfo = NULL;
2629         }
2631         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
2633         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
2634     }
2638 /**
2639  * Select multiple items from list
2640  *
2641  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2642  */
2643 class admin_setting_configmultiselect extends admin_setting_configselect {
2644     /**
2645      * Constructor
2646      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2647      * @param string $visiblename localised
2648      * @param string $description long localised info
2649      * @param array $defaultsetting array of selected items
2650      * @param array $choices array of $value=>$label for each list item
2651      */
2652     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2653         parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
2654     }
2656     /**
2657      * Returns the select setting(s)
2658      *
2659      * @return mixed null or array. Null if no settings else array of setting(s)
2660      */
2661     public function get_setting() {
2662         $result = $this->config_read($this->name);
2663         if (is_null($result)) {
2664             return NULL;
2665         }
2666         if ($result === '') {
2667             return array();
2668         }
2669         return explode(',', $result);
2670     }
2672     /**
2673      * Saves setting(s) provided through $data
2674      *
2675      * Potential bug in the works should anyone call with this function
2676      * using a vartype that is not an array
2677      *
2678      * @param array $data
2679      */
2680     public function write_setting($data) {
2681         if (!is_array($data)) {
2682             return ''; //ignore it
2683         }
2684         if (!$this->load_choices() or empty($this->choices)) {
2685             return '';
2686         }
2688         unset($data['xxxxx']);
2690         $save = array();
2691         foreach ($data as $value) {
2692             if (!array_key_exists($value, $this->choices)) {
2693                 continue; // ignore it
2694             }
2695             $save[] = $value;
2696         }
2698         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2699     }
2701     /**
2702      * Is setting related to query text - used when searching
2703      *
2704      * @param string $query
2705      * @return bool true if related, false if not
2706      */
2707     public function is_related($query) {
2708         if (!$this->load_choices() or empty($this->choices)) {
2709             return false;
2710         }
2711         if (parent::is_related($query)) {
2712             return true;
2713         }
2715         foreach ($this->choices as $desc) {
2716             if (strpos(textlib::strtolower($desc), $query) !== false) {
2717                 return true;
2718             }
2719         }
2720         return false;
2721     }
2723     /**
2724      * Returns XHTML multi-select field
2725      *
2726      * @todo Add vartype handling to ensure $data is an array
2727      * @param array $data Array of values to select by default
2728      * @param string $query
2729      * @return string XHTML multi-select field
2730      */
2731     public function output_html($data, $query='') {
2732         if (!$this->load_choices() or empty($this->choices)) {
2733             return '';
2734         }
2735         $choices = $this->choices;
2736         $default = $this->get_defaultsetting();
2737         if (is_null($default)) {
2738             $default = array();
2739         }
2740         if (is_null($data)) {
2741             $data = array();
2742         }
2744         $defaults = array();
2745         $size = min(10, count($this->choices));
2746         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2747         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
2748         foreach ($this->choices as $key => $description) {
2749             if (in_array($key, $data)) {
2750                 $selected = 'selected="selected"';
2751             } else {
2752                 $selected = '';
2753             }
2754             if (in_array($key, $default)) {
2755                 $defaults[] = $description;
2756             }
2758             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2759         }
2761         if (is_null($default)) {
2762             $defaultinfo = NULL;
2763         } if (!empty($defaults)) {
2764             $defaultinfo = implode(', ', $defaults);
2765         } else {
2766             $defaultinfo = get_string('none');
2767         }
2769         $return .= '</select></div>';
2770         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
2771     }
2774 /**
2775  * Time selector
2776  *
2777  * This is a liiitle bit messy. we're using two selects, but we're returning
2778  * them as an array named after $name (so we only use $name2 internally for the setting)
2779  *
2780  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2781  */
2782 class admin_setting_configtime extends admin_setting {
2783     /** @var string Used for setting second select (minutes) */
2784     public $name2;
2786     /**
2787      * Constructor
2788      * @param string $hoursname setting for hours
2789      * @param string $minutesname setting for hours
2790      * @param string $visiblename localised
2791      * @param string $description long localised info
2792      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2793      */
2794     public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
2795         $this->name2 = $minutesname;
2796         parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
2797     }
2799     /**
2800      * Get the selected time
2801      *
2802      * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
2803      */
2804     public function get_setting() {
2805         $result1 = $this->config_read($this->name);
2806         $result2 = $this->config_read($this->name2);
2807         if (is_null($result1) or is_null($result2)) {
2808             return NULL;
2809         }
2811         return array('h' => $result1, 'm' => $result2);
2812     }
2814     /**
2815      * Store the time (hours and minutes)
2816      *
2817      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2818      * @return bool true if success, false if not
2819      */
2820     public function write_setting($data) {
2821         if (!is_array($data)) {
2822             return '';
2823         }
2825         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2826         return ($result ? '' : get_string('errorsetting', 'admin'));
2827     }
2829     /**
2830      * Returns XHTML time select fields
2831      *
2832      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2833      * @param string $query
2834      * @return string XHTML time select fields and wrapping div(s)
2835      */
2836     public function output_html($data, $query='') {
2837         $default = $this->get_defaultsetting();
2839         if (is_array($default)) {
2840             $defaultinfo = $default['h'].':'.$default['m'];
2841         } else {
2842             $defaultinfo = NULL;
2843         }
2845         $return = '<div class="form-time defaultsnext">'.
2846             '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
2847         for ($i = 0; $i < 24; $i++) {
2848             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2849         }
2850         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2851         for ($i = 0; $i < 60; $i += 5) {
2852             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2853         }
2854         $return .= '</select></div>';
2855         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2856     }
2861 /**
2862  * Seconds duration setting.
2863  *
2864  * @copyright 2012 Petr Skoda (http://skodak.org)
2865  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2866  */
2867 class admin_setting_configduration extends admin_setting {
2869     /** @var int default duration unit */
2870     protected $defaultunit;
2872     /**
2873      * Constructor
2874      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2875      *                     or 'myplugin/mysetting' for ones in config_plugins.
2876      * @param string $visiblename localised name
2877      * @param string $description localised long description
2878      * @param mixed $defaultsetting string or array depending on implementation
2879      * @param int $defaultunit - day, week, etc. (in seconds)
2880      */
2881     public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
2882         if (is_number($defaultsetting)) {
2883             $defaultsetting = self::parse_seconds($defaultsetting);
2884         }
2885         $units = self::get_units();
2886         if (isset($units[$defaultunit])) {
2887             $this->defaultunit = $defaultunit;
2888         } else {
2889             $this->defaultunit = 86400;
2890         }
2891         parent::__construct($name, $visiblename, $description, $defaultsetting);
2892     }
2894     /**
2895      * Returns selectable units.
2896      * @static
2897      * @return array
2898      */
2899     protected static function get_units() {
2900         return array(
2901             604800 => get_string('weeks'),
2902             86400 => get_string('days'),
2903             3600 => get_string('hours'),
2904             60 => get_string('minutes'),
2905             1 => get_string('seconds'),
2906         );
2907     }
2909     /**
2910      * Converts seconds to some more user friendly string.
2911      * @static
2912      * @param int $seconds
2913      * @return string
2914      */
2915     protected static function get_duration_text($seconds) {
2916         if (empty($seconds)) {
2917             return get_string('none');
2918         }
2919         $data = self::parse_seconds($seconds);
2920         switch ($data['u']) {
2921             case (60*60*24*7):
2922                 return get_string('numweeks', '', $data['v']);
2923             case (60*60*24):
2924                 return get_string('numdays', '', $data['v']);
2925             case (60*60):
2926                 return get_string('numhours', '', $data['v']);
2927             case (60):
2928                 return get_string('numminutes', '', $data['v']);
2929             default:
2930                 return get_string('numseconds', '', $data['v']*$data['u']);
2931         }
2932     }
2934     /**
2935      * Finds suitable units for given duration.
2936      * @static
2937      * @param int $seconds
2938      * @return array
2939      */
2940     protected static function parse_seconds($seconds) {
2941         foreach (self::get_units() as $unit => $unused) {
2942             if ($seconds % $unit === 0) {
2943                 return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
2944             }
2945         }
2946         return array('v'=>(int)$seconds, 'u'=>1);
2947     }
2949     /**
2950      * Get the selected duration as array.
2951      *
2952      * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
2953      */
2954     public function get_setting() {
2955         $seconds = $this->config_read($this->name);
2956         if (is_null($seconds)) {
2957             return null;
2958         }
2960         return self::parse_seconds($seconds);
2961     }
2963     /**
2964      * Store the duration as seconds.
2965      *
2966      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2967      * @return bool true if success, false if not
2968      */
2969     public function write_setting($data) {
2970         if (!is_array($data)) {
2971             return '';
2972         }
2974         $seconds = (int)($data['v']*$data['u']);
2975         if ($seconds < 0) {
2976             return get_string('errorsetting', 'admin');
2977         }
2979         $result = $this->config_write($this->name, $seconds);
2980         return ($result ? '' : get_string('errorsetting', 'admin'));
2981     }
2983     /**
2984      * Returns duration text+select fields.
2985      *
2986      * @param array $data Must be form 'v'=>xx, 'u'=>xx
2987      * @param string $query
2988      * @return string duration text+select fields and wrapping div(s)
2989      */
2990     public function output_html($data, $query='') {
2991         $default = $this->get_defaultsetting();
2993         if (is_number($default)) {
2994             $defaultinfo = self::get_duration_text($default);
2995         } else if (is_array($default)) {
2996             $defaultinfo = self::get_duration_text($default['v']*$default['u']);
2997         } else {
2998             $defaultinfo = null;
2999         }
3001         $units = self::get_units();
3003         $return = '<div class="form-duration defaultsnext">';
3004         $return .= '<input type="text" size="5" id="'.$this->get_id().'v" name="'.$this->get_full_name().'[v]" value="'.s($data['v']).'" />';
3005         $return .= '<select id="'.$this->get_id().'u" name="'.$this->get_full_name().'[u]">';
3006         foreach ($units as $val => $text) {
3007             $selected = '';
3008             if ($data['v'] == 0) {
3009                 if ($val == $this->defaultunit) {
3010                     $selected = ' selected="selected"';
3011                 }
3012             } else if ($val == $data['u']) {
3013                 $selected = ' selected="selected"';
3014             }
3015             $return .= '<option value="'.$val.'"'.$selected.'>'.$text.'</option>';
3016         }
3017         $return .= '</select></div>';
3018         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
3019     }
3023 /**
3024  * Used to validate a textarea used for ip addresses
3025  *
3026  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3027  */
3028 class admin_setting_configiplist extends admin_setting_configtextarea {
3030     /**
3031      * Validate the contents of the textarea as IP addresses
3032      *
3033      * Used to validate a new line separated list of IP addresses collected from
3034      * a textarea control
3035      *
3036      * @param string $data A list of IP Addresses separated by new lines
3037      * @return mixed bool true for success or string:error on failure
3038      */
3039     public function validate($data) {
3040         if(!empty($data)) {
3041             $ips = explode("\n", $data);
3042         } else {
3043             return true;
3044         }
3045         $result = true;
3046         foreach($ips as $ip) {
3047             $ip = trim($ip);
3048             if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
3049                 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
3050                 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
3051                 $result = true;
3052             } else {
3053                 $result = false;
3054                 break;
3055             }
3056         }
3057         if($result) {
3058             return true;
3059         } else {
3060             return get_string('validateerror', 'admin');
3061         }
3062     }
3066 /**
3067  * An admin setting for selecting one or more users who have a capability
3068  * in the system context
3069  *
3070  * An admin setting for selecting one or more users, who have a particular capability
3071  * in the system context. Warning, make sure the list will never be too long. There is
3072  * no paging or searching of this list.
3073  *
3074  * To correctly get a list of users from this config setting, you need to call the
3075  * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
3076  *
3077  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3078  */
3079 class admin_setting_users_with_capability extends admin_setting_configmultiselect {
3080     /** @var string The capabilities name */
3081     protected $capability;
3082     /** @var int include admin users too */
3083     protected $includeadmins;
3085     /**
3086      * Constructor.
3087      *
3088      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3089      * @param string $visiblename localised name
3090      * @param string $description localised long description
3091      * @param array $defaultsetting array of usernames
3092      * @param string $capability string capability name.
3093      * @param bool $includeadmins include administrators
3094      */
3095     function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
3096         $this->capability    = $capability;
3097         $this->includeadmins = $includeadmins;
3098         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
3099     }
3101     /**
3102      * Load all of the uses who have the capability into choice array
3103      *
3104      * @return bool Always returns true
3105      */
3106     function load_choices() {
3107         if (is_array($this->choices)) {
3108             return true;
3109         }
3110         list($sort, $sortparams) = users_order_by_sql('u');
3111         if (!empty($sortparams)) {
3112             throw new coding_exception('users_order_by_sql returned some query parameters. ' .
3113                     'This is unexpected, and a problem because there is no way to pass these ' .
3114                     'parameters to get_users_by_capability. See MDL-34657.');
3115         }
3116         $users = get_users_by_capability(context_system::instance(),
3117                 $this->capability, 'u.id,u.username,u.firstname,u.lastname', $sort);
3118         $this->choices = array(
3119             '$@NONE@$' => get_string('nobody'),
3120             '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
3121         );
3122         if ($this->includeadmins) {
3123             $admins = get_admins();
3124             foreach ($admins as $user) {
3125                 $this->choices[$user->id] = fullname($user);
3126             }
3127         }
3128         if (is_array($users)) {
3129             foreach ($users as $user) {
3130                 $this->choices[$user->id] = fullname($user);
3131             }
3132         }
3133         return true;
3134     }
3136     /**
3137      * Returns the default setting for class
3138      *
3139      * @return mixed Array, or string. Empty string if no default
3140      */
3141     public function get_defaultsetting() {
3142         $this->load_choices();
3143         $defaultsetting = parent::get_defaultsetting();
3144         if (empty($defaultsetting)) {
3145             return array('$@NONE@$');
3146         } else if (array_key_exists($defaultsetting, $this->choices)) {
3147                 return $defaultsetting;
3148             } else {
3149                 return '';
3150             }
3151     }
3153     /**
3154      * Returns the current setting
3155      *
3156      * @return mixed array or string
3157      */
3158     public function get_setting() {
3159         $result = parent::get_setting();
3160         if ($result === null) {
3161             // this is necessary for settings upgrade
3162             return null;
3163         }
3164         if (empty($result)) {
3165             $result = array('$@NONE@$');
3166         }
3167         return $result;
3168     }
3170     /**
3171      * Save the chosen setting provided as $data
3172      *
3173      * @param array $data
3174      * @return mixed string or array
3175      */
3176     public function write_setting($data) {
3177     // If all is selected, remove any explicit options.
3178         if (in_array('$@ALL@$', $data)) {
3179             $data = array('$@ALL@$');
3180         }
3181         // None never needs to be written to the DB.
3182         if (in_array('$@NONE@$', $data)) {
3183             unset($data[array_search('$@NONE@$', $data)]);
3184         }
3185         return parent::write_setting($data);
3186     }
3190 /**
3191  * Special checkbox for calendar - resets SESSION vars.
3192  *
3193  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3194  */
3195 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
3196     /**
3197      * Calls the parent::__construct with default values
3198      *
3199      * name =>  calendar_adminseesall
3200      * visiblename => get_string('adminseesall', 'admin')
3201      * description => get_string('helpadminseesall', 'admin')
3202      * defaultsetting => 0
3203      */
3204     public function __construct() {
3205         parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
3206             get_string('helpadminseesall', 'admin'), '0');
3207     }
3209     /**
3210      * Stores the setting passed in $data
3211      *
3212      * @param mixed gets converted to string for comparison
3213      * @return string empty string or error message
3214      */
3215     public function write_setting($data) {
3216         global $SESSION;
3217         return parent::write_setting($data);
3218     }
3221 /**
3222  * Special select for settings that are altered in setup.php and can not be altered on the fly
3223  *
3224  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3225  */
3226 class admin_setting_special_selectsetup extends admin_setting_configselect {
3227     /**
3228      * Reads the setting directly from the database
3229      *
3230      * @return mixed
3231      */
3232     public function get_setting() {
3233     // read directly from db!
3234         return get_config(NULL, $this->name);
3235     }
3237     /**
3238      * Save the setting passed in $data
3239      *
3240      * @param string $data The setting to save
3241      * @return string empty or error message
3242      */
3243     public function write_setting($data) {
3244         global $CFG;
3245         // do not change active CFG setting!
3246         $current = $CFG->{$this->name};
3247         $result = parent::write_setting($data);
3248         $CFG->{$this->name} = $current;
3249         return $result;
3250     }
3254 /**
3255  * Special select for frontpage - stores data in course table
3256  *
3257  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3258  */
3259 class admin_setting_sitesetselect extends admin_setting_configselect {
3260     /**
3261      * Returns the site name for the selected site
3262      *
3263      * @see get_site()
3264      * @return string The site name of the selected site
3265      */
3266     public function get_setting() {
3267         $site = course_get_format(get_site())->get_course();
3268         return $site->{$this->name};
3269     }
3271     /**
3272      * Updates the database and save the setting
3273      *
3274      * @param string data
3275      * @return string empty or error message
3276      */
3277     public function write_setting($data) {
3278         global $DB, $SITE;
3279         if (!in_array($data, array_keys($this->choices))) {
3280             return get_string('errorsetting', 'admin');
3281         }
3282         $record = new stdClass();
3283         $record->id           = SITEID;
3284         $temp                 = $this->name;
3285         $record->$temp        = $data;
3286         $record->timemodified = time();
3287         // update $SITE
3288         $SITE->{$this->name} = $data;
3289         course_get_format($SITE)->update_course_format_options($record);
3290         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3291     }
3295 /**
3296  * Select for blog's bloglevel setting: if set to 0, will set blog_menu
3297  * block to hidden.
3298  *
3299  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3300  */
3301 class admin_setting_bloglevel extends admin_setting_configselect {
3302     /**
3303      * Updates the database and save the setting
3304      *
3305      * @param string data
3306      * @return string empty or error message
3307      */
3308     public function write_setting($data) {
3309         global $DB, $CFG;
3310         if ($data == 0) {
3311             $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
3312             foreach ($blogblocks as $block) {
3313                 $DB->set_field('block', 'visible', 0, array('id' => $block->id));
3314             }
3315         } else {
3316             // reenable all blocks only when switching from disabled blogs
3317             if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
3318                 $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
3319                 foreach ($blogblocks as $block) {
3320                     $DB->set_field('block', 'visible', 1, array('id' => $block->id));
3321                 }
3322             }
3323         }
3324         return parent::write_setting($data);
3325     }
3329 /**
3330  * Special select - lists on the frontpage - hacky
3331  *
3332  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3333  */
3334 class admin_setting_courselist_frontpage extends admin_setting {
3335     /** @var array Array of choices value=>label */
3336     public $choices;
3338     /**
3339      * Construct override, requires one param
3340      *
3341      * @param bool $loggedin Is the user logged in
3342      */
3343     public function __construct($loggedin) {
3344         global $CFG;
3345         require_once($CFG->dirroot.'/course/lib.php');
3346         $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
3347         $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
3348         $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
3349         $defaults    = array(FRONTPAGECOURSELIST);
3350         parent::__construct($name, $visiblename, $description, $defaults);
3351     }
3353     /**
3354      * Loads the choices available
3355      *
3356      * @return bool always returns true
3357      */
3358     public function load_choices() {
3359         global $DB;
3360         if (is_array($this->choices)) {
3361             return true;
3362         }
3363         $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
3364             FRONTPAGECOURSELIST    => get_string('frontpagecourselist'),
3365             FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
3366             FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
3367             'none'                 => get_string('none'));
3368         if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
3369             unset($this->choices[FRONTPAGECOURSELIST]);
3370         }
3371         return true;
3372     }
3374     /**
3375      * Returns the selected settings
3376      *
3377      * @param mixed array or setting or null
3378      */
3379     public function get_setting() {
3380         $result = $this->config_read($this->name);
3381         if (is_null($result)) {
3382             return NULL;
3383         }
3384         if ($result === '') {
3385             return array();
3386         }
3387         return explode(',', $result);
3388     }
3390     /**
3391      * Save the selected options
3392      *
3393      * @param array $data
3394      * @return mixed empty string (data is not an array) or bool true=success false=failure
3395      */
3396     public function write_setting($data) {
3397         if (!is_array($data)) {
3398             return '';
3399         }
3400         $this->load_choices();
3401         $save = array();
3402         foreach($data as $datum) {
3403             if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
3404                 continue;
3405             }
3406             $save[$datum] = $datum; // no duplicates
3407         }
3408         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3409     }
3411     /**
3412      * Return XHTML select field and wrapping div
3413      *
3414      * @todo Add vartype handling to make sure $data is an array
3415      * @param array $data Array of elements to select by default
3416      * @return string XHTML select field and wrapping div
3417      */
3418     public function output_html($data, $query='') {
3419         $this->load_choices();
3420         $currentsetting = array();
3421         foreach ($data as $key) {
3422             if ($key != 'none' and array_key_exists($key, $this->choices)) {
3423                 $currentsetting[] = $key; // already selected first
3424             }
3425         }
3427         $return = '<div class="form-group">';
3428         for ($i = 0; $i < count($this->choices) - 1; $i++) {
3429             if (!array_key_exists($i, $currentsetting)) {
3430                 $currentsetting[$i] = 'none'; //none
3431             }
3432             $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
3433             foreach ($this->choices as $key => $value) {
3434                 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
3435             }
3436             $return .= '</select>';
3437             if ($i !== count($this->choices) - 2) {
3438                 $return .= '<br />';
3439             }
3440         }
3441         $return .= '</div>';
3443         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3444     }
3448 /**
3449  * Special checkbox for frontpage - stores data in course table
3450  *
3451  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3452  */
3453 class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
3454     /**
3455      * Returns the current sites name
3456      *
3457      * @return string
3458      */
3459     public function get_setting() {
3460         $site = course_get_format(get_site())->get_course();
3461         return $site->{$this->name};
3462     }
3464     /**
3465      * Save the selected setting
3466      *
3467      * @param string $data The selected site
3468      * @return string empty string or error message
3469      */
3470     public function write_setting($data) {
3471         global $DB, $SITE;
3472         $record = new stdClass();
3473         $record->id            = $SITE->id;
3474         $record->{$this->name} = ($data == '1' ? 1 : 0);
3475         $record->timemodified  = time();
3476         // update $SITE
3477         $SITE->{$this->name} = $data;
3478         course_get_format($SITE)->update_course_format_options($record);
3479         $DB->update_record('course', $record);
3480         // There is something wrong in cache updates somewhere, let's reset everything.
3481         format_base::reset_course_cache();
3482         return '';
3483     }
3486 /**
3487  * Special text for frontpage - stores data in course table.
3488  * Empty string means not set here. Manual setting is required.
3489  *
3490  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3491  */
3492 class admin_setting_sitesettext extends admin_setting_configtext {
3493     /**
3494      * Return the current setting
3495      *
3496      * @return mixed string or null
3497      */
3498     public function get_setting() {
3499         $site = course_get_format(get_site())->get_course();
3500         return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
3501     }
3503     /**
3504      * Validate the selected data
3505      *
3506      * @param string $data The selected value to validate
3507      * @return mixed true or message string
3508      */
3509     public function validate($data) {
3510         $cleaned = clean_param($data, PARAM_TEXT);
3511         if ($cleaned === '') {
3512             return get_string('required');
3513         }
3514         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
3515             return true;
3516         } else {
3517             return get_string('validateerror', 'admin');
3518         }
3519     }
3521     /**
3522      * Save the selected setting
3523      *
3524      * @param string $data The selected value
3525      * @return string empty or error message
3526      */
3527     public function write_setting($data) {
3528         global $DB, $SITE;
3529         $data = trim($data);
3530         $validated = $this->validate($data);
3531         if ($validated !== true) {
3532             return $validated;
3533         }
3535         $record = new stdClass();
3536         $record->id            = $SITE->id;
3537         $record->{$this->name} = $data;
3538         $record->timemodified  = time();
3539         // update $SITE
3540         $SITE->{$this->name} = $data;
3541         course_get_format($SITE)->update_course_format_options($record);
3542         $DB->update_record('course', $record);
3543         // There is something wrong in cache updates somewhere, let's reset everything.
3544         format_base::reset_course_cache();
3545         return '';
3546     }
3550 /**
3551  * Special text editor for site description.
3552  *
3553  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3554  */
3555 class admin_setting_special_frontpagedesc extends admin_setting {
3556     /**
3557      * Calls parent::__construct with specific arguments
3558      */
3559     public function __construct() {
3560         parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
3561         editors_head_setup();
3562     }
3564     /**
3565      * Return the current setting
3566      * @return string The current setting
3567      */
3568     public function get_setting() {
3569         $site = course_get_format(get_site())->get_course();
3570         return $site->{$this->name};
3571     }
3573     /**
3574      * Save the new setting
3575      *
3576      * @param string $data The new value to save
3577      * @return string empty or error message
3578      */
3579     public function write_setting($data) {
3580         global $DB, $SITE;
3581         $record = new stdClass();
3582         $record->id            = $SITE->id;
3583         $record->{$this->name} = $data;
3584         $record->timemodified  = time();
3585         $SITE->{$this->name} = $data;
3586         course_get_format($SITE)->update_course_format_options($record);
3587         $DB->update_record('course', $record);
3588         // There is something wrong in cache updates somewhere, let's reset everything.
3589         format_base::reset_course_cache();
3590         return '';
3591     }
3593     /**
3594      * Returns XHTML for the field plus wrapping div
3595      *
3596      * @param string $data The current value
3597      * @param string $query
3598      * @return string The XHTML output
3599      */
3600     public function output_html($data, $query='') {
3601         global $CFG;
3603         $CFG->adminusehtmleditor = can_use_html_editor();
3604         $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
3606         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3607     }
3611 /**
3612  * Administration interface for emoticon_manager settings.
3613  *
3614  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3615  */
3616 class admin_setting_emoticons extends admin_setting {
3618     /**
3619      * Calls parent::__construct with specific args
3620      */
3621     public function __construct() {
3622         global $CFG;
3624         $manager = get_emoticon_manager();
3625         $defaults = $this->prepare_form_data($manager->default_emoticons());
3626         parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
3627     }
3629     /**
3630      * Return the current setting(s)
3631      *
3632      * @return array Current settings array
3633      */
3634     public function get_setting() {
3635         global $CFG;
3637         $manager = get_emoticon_manager();
3639         $config = $this->config_read($this->name);
3640         if (is_null($config)) {
3641             return null;
3642         }
3644         $config = $manager->decode_stored_config($config);
3645         if (is_null($config)) {
3646             return null;
3647         }
3649         return $this->prepare_form_data($config);
3650     }
3652     /**
3653      * Save selected settings
3654      *
3655      * @param array $data Array of settings to save
3656      * @return bool
3657      */
3658     public function write_setting($data) {
3660         $manager = get_emoticon_manager();
3661         $emoticons = $this->process_form_data($data);
3663         if ($emoticons === false) {
3664             return false;
3665         }
3667         if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
3668