e5b47608348765ba55a8f6bc1d8df4cafa0139e8
[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     // recursively uninstall all module/editor subplugins first
127     if ($type === 'mod' || $type === 'editor') {
128         $base = get_component_directory($type . '_' . $name);
129         if (file_exists("$base/db/subplugins.php")) {
130             $subplugins = array();
131             include("$base/db/subplugins.php");
132             foreach ($subplugins as $subplugintype=>$dir) {
133                 $instances = get_plugin_list($subplugintype);
134                 foreach ($instances as $subpluginname => $notusedpluginpath) {
135                     uninstall_plugin($subplugintype, $subpluginname);
136                 }
137             }
138         }
140     }
142     $component = $type . '_' . $name;  // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
144     if ($type === 'mod') {
145         $pluginname = $name;  // eg. 'forum'
146         if (get_string_manager()->string_exists('modulename', $component)) {
147             $strpluginname = get_string('modulename', $component);
148         } else {
149             $strpluginname = $component;
150         }
152     } else {
153         $pluginname = $component;
154         if (get_string_manager()->string_exists('pluginname', $component)) {
155             $strpluginname = get_string('pluginname', $component);
156         } else {
157             $strpluginname = $component;
158         }
159     }
161     echo $OUTPUT->heading($pluginname);
163     $plugindirectory = get_plugin_directory($type, $name);
164     $uninstalllib = $plugindirectory . '/db/uninstall.php';
165     if (file_exists($uninstalllib)) {
166         require_once($uninstalllib);
167         $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall';    // eg. 'xmldb_workshop_uninstall()'
168         if (function_exists($uninstallfunction)) {
169             if (!$uninstallfunction()) {
170                 echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $pluginname);
171             }
172         }
173     }
175     if ($type === 'mod') {
176         // perform cleanup tasks specific for activity modules
178         if (!$module = $DB->get_record('modules', array('name' => $name))) {
179             print_error('moduledoesnotexist', 'error');
180         }
182         // delete all the relevant instances from all course sections
183         if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) {
184             foreach ($coursemods as $coursemod) {
185                 if (!delete_mod_from_section($coursemod->id, $coursemod->section)) {
186                     echo $OUTPUT->notification("Could not delete the $strpluginname with id = $coursemod->id from section $coursemod->section");
187                 }
188             }
189         }
191         // clear course.modinfo for courses that used this module
192         $sql = "UPDATE {course}
193                    SET modinfo=''
194                  WHERE id IN (SELECT DISTINCT course
195                                 FROM {course_modules}
196                                WHERE module=?)";
197         $DB->execute($sql, array($module->id));
199         // delete all the course module records
200         $DB->delete_records('course_modules', array('module' => $module->id));
202         // delete module contexts
203         if ($coursemods) {
204             foreach ($coursemods as $coursemod) {
205                 if (!delete_context(CONTEXT_MODULE, $coursemod->id)) {
206                     echo $OUTPUT->notification("Could not delete the context for $strpluginname with id = $coursemod->id");
207                 }
208             }
209         }
211         // delete the module entry itself
212         $DB->delete_records('modules', array('name' => $module->name));
214         // cleanup the gradebook
215         require_once($CFG->libdir.'/gradelib.php');
216         grade_uninstalled_module($module->name);
218         // Perform any custom uninstall tasks
219         if (file_exists($CFG->dirroot . '/mod/' . $module->name . '/lib.php')) {
220             require_once($CFG->dirroot . '/mod/' . $module->name . '/lib.php');
221             $uninstallfunction = $module->name . '_uninstall';
222             if (function_exists($uninstallfunction)) {
223                 debugging("{$uninstallfunction}() has been deprecated. Use the plugin's db/uninstall.php instead", DEBUG_DEVELOPER);
224                 if (!$uninstallfunction()) {
225                     echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $module->name.'!');
226                 }
227             }
228         }
230     } else if ($type === 'enrol') {
231         // NOTE: this is a bit brute force way - it will not trigger events and hooks properly
232         // nuke all role assignments
233         role_unassign_all(array('component'=>$component));
234         // purge participants
235         $DB->delete_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($name));
236         // purge enrol instances
237         $DB->delete_records('enrol', array('enrol'=>$name));
238         // tweak enrol settings
239         if (!empty($CFG->enrol_plugins_enabled)) {
240             $enabledenrols = explode(',', $CFG->enrol_plugins_enabled);
241             $enabledenrols = array_unique($enabledenrols);
242             $enabledenrols = array_flip($enabledenrols);
243             unset($enabledenrols[$name]);
244             $enabledenrols = array_flip($enabledenrols);
245             if (is_array($enabledenrols)) {
246                 set_config('enrol_plugins_enabled', implode(',', $enabledenrols));
247             }
248         }
250     } else if ($type === 'block') {
251         if ($block = $DB->get_record('block', array('name'=>$name))) {
252             // Inform block it's about to be deleted
253             if (file_exists("$CFG->dirroot/blocks/$block->name/block_$block->name.php")) {
254                 $blockobject = block_instance($block->name);
255                 if ($blockobject) {
256                     $blockobject->before_delete();  //only if we can create instance, block might have been already removed
257                 }
258             }
260             // First delete instances and related contexts
261             $instances = $DB->get_records('block_instances', array('blockname' => $block->name));
262             foreach($instances as $instance) {
263                 blocks_delete_instance($instance);
264             }
266             // Delete block
267             $DB->delete_records('block', array('id'=>$block->id));
268         }
269     } else if ($type === 'format') {
270         if (($defaultformat = get_config('moodlecourse', 'format')) && $defaultformat !== $name) {
271             $courses = $DB->get_records('course', array('format' => $name), 'id');
272             $data = (object)array('id' => null, 'format' => $defaultformat);
273             foreach ($courses as $record) {
274                 $data->id = $record->id;
275                 update_course($data);
276             }
277         }
278         $DB->delete_records('course_format_options', array('format' => $name));
279     }
281     // perform clean-up task common for all the plugin/subplugin types
283     //delete the web service functions and pre-built services
284     require_once($CFG->dirroot.'/lib/externallib.php');
285     external_delete_descriptions($component);
287     // delete calendar events
288     $DB->delete_records('event', array('modulename' => $pluginname));
290     // delete all the logs
291     $DB->delete_records('log', array('module' => $pluginname));
293     // delete log_display information
294     $DB->delete_records('log_display', array('component' => $component));
296     // delete the module configuration records
297     unset_all_config_for_plugin($pluginname);
299     // delete message provider
300     message_provider_uninstall($component);
302     // delete message processor
303     if ($type === 'message') {
304         message_processor_uninstall($name);
305     }
307     // delete the plugin tables
308     $xmldbfilepath = $plugindirectory . '/db/install.xml';
309     drop_plugin_tables($component, $xmldbfilepath, false);
310     if ($type === 'mod' or $type === 'block') {
311         // non-frankenstyle table prefixes
312         drop_plugin_tables($name, $xmldbfilepath, false);
313     }
315     // delete the capabilities that were defined by this module
316     capabilities_cleanup($component);
318     // remove event handlers and dequeue pending events
319     events_uninstall($component);
321     echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
324 /**
325  * Returns the version of installed component
326  *
327  * @param string $component component name
328  * @param string $source either 'disk' or 'installed' - where to get the version information from
329  * @return string|bool version number or false if the component is not found
330  */
331 function get_component_version($component, $source='installed') {
332     global $CFG, $DB;
334     list($type, $name) = normalize_component($component);
336     // moodle core or a core subsystem
337     if ($type === 'core') {
338         if ($source === 'installed') {
339             if (empty($CFG->version)) {
340                 return false;
341             } else {
342                 return $CFG->version;
343             }
344         } else {
345             if (!is_readable($CFG->dirroot.'/version.php')) {
346                 return false;
347             } else {
348                 $version = null; //initialize variable for IDEs
349                 include($CFG->dirroot.'/version.php');
350                 return $version;
351             }
352         }
353     }
355     // activity module
356     if ($type === 'mod') {
357         if ($source === 'installed') {
358             return $DB->get_field('modules', 'version', array('name'=>$name));
359         } else {
360             $mods = get_plugin_list('mod');
361             if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
362                 return false;
363             } else {
364                 $module = new stdclass();
365                 include($mods[$name].'/version.php');
366                 return $module->version;
367             }
368         }
369     }
371     // block
372     if ($type === 'block') {
373         if ($source === 'installed') {
374             return $DB->get_field('block', 'version', array('name'=>$name));
375         } else {
376             $blocks = get_plugin_list('block');
377             if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
378                 return false;
379             } else {
380                 $plugin = new stdclass();
381                 include($blocks[$name].'/version.php');
382                 return $plugin->version;
383             }
384         }
385     }
387     // all other plugin types
388     if ($source === 'installed') {
389         return get_config($type.'_'.$name, 'version');
390     } else {
391         $plugins = get_plugin_list($type);
392         if (empty($plugins[$name])) {
393             return false;
394         } else {
395             $plugin = new stdclass();
396             include($plugins[$name].'/version.php');
397             return $plugin->version;
398         }
399     }
402 /**
403  * Delete all plugin tables
404  *
405  * @param string $name Name of plugin, used as table prefix
406  * @param string $file Path to install.xml file
407  * @param bool $feedback defaults to true
408  * @return bool Always returns true
409  */
410 function drop_plugin_tables($name, $file, $feedback=true) {
411     global $CFG, $DB;
413     // first try normal delete
414     if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
415         return true;
416     }
418     // then try to find all tables that start with name and are not in any xml file
419     $used_tables = get_used_table_names();
421     $tables = $DB->get_tables();
423     /// Iterate over, fixing id fields as necessary
424     foreach ($tables as $table) {
425         if (in_array($table, $used_tables)) {
426             continue;
427         }
429         if (strpos($table, $name) !== 0) {
430             continue;
431         }
433         // found orphan table --> delete it
434         if ($DB->get_manager()->table_exists($table)) {
435             $xmldb_table = new xmldb_table($table);
436             $DB->get_manager()->drop_table($xmldb_table);
437         }
438     }
440     return true;
443 /**
444  * Returns names of all known tables == tables that moodle knows about.
445  *
446  * @return array Array of lowercase table names
447  */
448 function get_used_table_names() {
449     $table_names = array();
450     $dbdirs = get_db_directories();
452     foreach ($dbdirs as $dbdir) {
453         $file = $dbdir.'/install.xml';
455         $xmldb_file = new xmldb_file($file);
457         if (!$xmldb_file->fileExists()) {
458             continue;
459         }
461         $loaded    = $xmldb_file->loadXMLStructure();
462         $structure = $xmldb_file->getStructure();
464         if ($loaded and $tables = $structure->getTables()) {
465             foreach($tables as $table) {
466                 $table_names[] = strtolower($table->getName());
467             }
468         }
469     }
471     return $table_names;
474 /**
475  * Returns list of all directories where we expect install.xml files
476  * @return array Array of paths
477  */
478 function get_db_directories() {
479     global $CFG;
481     $dbdirs = array();
483     /// First, the main one (lib/db)
484     $dbdirs[] = $CFG->libdir.'/db';
486     /// Then, all the ones defined by get_plugin_types()
487     $plugintypes = get_plugin_types();
488     foreach ($plugintypes as $plugintype => $pluginbasedir) {
489         if ($plugins = get_plugin_list($plugintype)) {
490             foreach ($plugins as $plugin => $plugindir) {
491                 $dbdirs[] = $plugindir.'/db';
492             }
493         }
494     }
496     return $dbdirs;
499 /**
500  * Try to obtain or release the cron lock.
501  * @param string  $name  name of lock
502  * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionally
503  * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
504  * @return bool true if lock obtained
505  */
506 function set_cron_lock($name, $until, $ignorecurrent=false) {
507     global $DB;
508     if (empty($name)) {
509         debugging("Tried to get a cron lock for a null fieldname");
510         return false;
511     }
513     // remove lock by force == remove from config table
514     if (is_null($until)) {
515         set_config($name, null);
516         return true;
517     }
519     if (!$ignorecurrent) {
520         // read value from db - other processes might have changed it
521         $value = $DB->get_field('config', 'value', array('name'=>$name));
523         if ($value and $value > time()) {
524             //lock active
525             return false;
526         }
527     }
529     set_config($name, $until);
530     return true;
533 /**
534  * Test if and critical warnings are present
535  * @return bool
536  */
537 function admin_critical_warnings_present() {
538     global $SESSION;
540     if (!has_capability('moodle/site:config', context_system::instance())) {
541         return 0;
542     }
544     if (!isset($SESSION->admin_critical_warning)) {
545         $SESSION->admin_critical_warning = 0;
546         if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
547             $SESSION->admin_critical_warning = 1;
548         }
549     }
551     return $SESSION->admin_critical_warning;
554 /**
555  * Detects if float supports at least 10 decimal digits
556  *
557  * Detects if float supports at least 10 decimal digits
558  * and also if float-->string conversion works as expected.
559  *
560  * @return bool true if problem found
561  */
562 function is_float_problem() {
563     $num1 = 2009010200.01;
564     $num2 = 2009010200.02;
566     return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
569 /**
570  * Try to verify that dataroot is not accessible from web.
571  *
572  * Try to verify that dataroot is not accessible from web.
573  * It is not 100% correct but might help to reduce number of vulnerable sites.
574  * Protection from httpd.conf and .htaccess is not detected properly.
575  *
576  * @uses INSECURE_DATAROOT_WARNING
577  * @uses INSECURE_DATAROOT_ERROR
578  * @param bool $fetchtest try to test public access by fetching file, default false
579  * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
580  */
581 function is_dataroot_insecure($fetchtest=false) {
582     global $CFG;
584     $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
586     $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
587     $rp = strrev(trim($rp, '/'));
588     $rp = explode('/', $rp);
589     foreach($rp as $r) {
590         if (strpos($siteroot, '/'.$r.'/') === 0) {
591             $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
592         } else {
593             break; // probably alias root
594         }
595     }
597     $siteroot = strrev($siteroot);
598     $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
600     if (strpos($dataroot, $siteroot) !== 0) {
601         return false;
602     }
604     if (!$fetchtest) {
605         return INSECURE_DATAROOT_WARNING;
606     }
608     // now try all methods to fetch a test file using http protocol
610     $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
611     preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
612     $httpdocroot = $matches[1];
613     $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
614     make_upload_directory('diag');
615     $testfile = $CFG->dataroot.'/diag/public.txt';
616     if (!file_exists($testfile)) {
617         file_put_contents($testfile, 'test file, do not delete');
618     }
619     $teststr = trim(file_get_contents($testfile));
620     if (empty($teststr)) {
621     // hmm, strange
622         return INSECURE_DATAROOT_WARNING;
623     }
625     $testurl = $datarooturl.'/diag/public.txt';
626     if (extension_loaded('curl') and
627         !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
628         !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
629         ($ch = @curl_init($testurl)) !== false) {
630         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
631         curl_setopt($ch, CURLOPT_HEADER, false);
632         $data = curl_exec($ch);
633         if (!curl_errno($ch)) {
634             $data = trim($data);
635             if ($data === $teststr) {
636                 curl_close($ch);
637                 return INSECURE_DATAROOT_ERROR;
638             }
639         }
640         curl_close($ch);
641     }
643     if ($data = @file_get_contents($testurl)) {
644         $data = trim($data);
645         if ($data === $teststr) {
646             return INSECURE_DATAROOT_ERROR;
647         }
648     }
650     preg_match('|https?://([^/]+)|i', $testurl, $matches);
651     $sitename = $matches[1];
652     $error = 0;
653     if ($fp = @fsockopen($sitename, 80, $error)) {
654         preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
655         $localurl = $matches[1];
656         $out = "GET $localurl HTTP/1.1\r\n";
657         $out .= "Host: $sitename\r\n";
658         $out .= "Connection: Close\r\n\r\n";
659         fwrite($fp, $out);
660         $data = '';
661         $incoming = false;
662         while (!feof($fp)) {
663             if ($incoming) {
664                 $data .= fgets($fp, 1024);
665             } else if (@fgets($fp, 1024) === "\r\n") {
666                     $incoming = true;
667                 }
668         }
669         fclose($fp);
670         $data = trim($data);
671         if ($data === $teststr) {
672             return INSECURE_DATAROOT_ERROR;
673         }
674     }
676     return INSECURE_DATAROOT_WARNING;
679 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
682 /**
683  * Interface for anything appearing in the admin tree
684  *
685  * The interface that is implemented by anything that appears in the admin tree
686  * block. It forces inheriting classes to define a method for checking user permissions
687  * and methods for finding something in the admin tree.
688  *
689  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
690  */
691 interface part_of_admin_tree {
693 /**
694  * Finds a named part_of_admin_tree.
695  *
696  * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
697  * and not parentable_part_of_admin_tree, then this function should only check if
698  * $this->name matches $name. If it does, it should return a reference to $this,
699  * otherwise, it should return a reference to NULL.
700  *
701  * If a class inherits parentable_part_of_admin_tree, this method should be called
702  * recursively on all child objects (assuming, of course, the parent object's name
703  * doesn't match the search criterion).
704  *
705  * @param string $name The internal name of the part_of_admin_tree we're searching for.
706  * @return mixed An object reference or a NULL reference.
707  */
708     public function locate($name);
710     /**
711      * Removes named part_of_admin_tree.
712      *
713      * @param string $name The internal name of the part_of_admin_tree we want to remove.
714      * @return bool success.
715      */
716     public function prune($name);
718     /**
719      * Search using query
720      * @param string $query
721      * @return mixed array-object structure of found settings and pages
722      */
723     public function search($query);
725     /**
726      * Verifies current user's access to this part_of_admin_tree.
727      *
728      * Used to check if the current user has access to this part of the admin tree or
729      * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
730      * then this method is usually just a call to has_capability() in the site context.
731      *
732      * If a class inherits parentable_part_of_admin_tree, this method should return the
733      * logical OR of the return of check_access() on all child objects.
734      *
735      * @return bool True if the user has access, false if she doesn't.
736      */
737     public function check_access();
739     /**
740      * Mostly useful for removing of some parts of the tree in admin tree block.
741      *
742      * @return True is hidden from normal list view
743      */
744     public function is_hidden();
746     /**
747      * Show we display Save button at the page bottom?
748      * @return bool
749      */
750     public function show_save();
754 /**
755  * Interface implemented by any part_of_admin_tree that has children.
756  *
757  * The interface implemented by any part_of_admin_tree that can be a parent
758  * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
759  * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
760  * include an add method for adding other part_of_admin_tree objects as children.
761  *
762  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
763  */
764 interface parentable_part_of_admin_tree extends part_of_admin_tree {
766 /**
767  * Adds a part_of_admin_tree object to the admin tree.
768  *
769  * Used to add a part_of_admin_tree object to this object or a child of this
770  * object. $something should only be added if $destinationname matches
771  * $this->name. If it doesn't, add should be called on child objects that are
772  * also parentable_part_of_admin_tree's.
773  *
774  * @param string $destinationname The internal name of the new parent for $something.
775  * @param part_of_admin_tree $something The object to be added.
776  * @return bool True on success, false on failure.
777  */
778     public function add($destinationname, $something);
783 /**
784  * The object used to represent folders (a.k.a. categories) in the admin tree block.
785  *
786  * Each admin_category object contains a number of part_of_admin_tree objects.
787  *
788  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
789  */
790 class admin_category implements parentable_part_of_admin_tree {
792     /** @var mixed An array of part_of_admin_tree objects that are this object's children */
793     public $children;
794     /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
795     public $name;
796     /** @var string The displayed name for this category. Usually obtained through get_string() */
797     public $visiblename;
798     /** @var bool Should this category be hidden in admin tree block? */
799     public $hidden;
800     /** @var mixed Either a string or an array or strings */
801     public $path;
802     /** @var mixed Either a string or an array or strings */
803     public $visiblepath;
805     /** @var array fast lookup category cache, all categories of one tree point to one cache */
806     protected $category_cache;
808     /**
809      * Constructor for an empty admin category
810      *
811      * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
812      * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
813      * @param bool $hidden hide category in admin tree block, defaults to false
814      */
815     public function __construct($name, $visiblename, $hidden=false) {
816         $this->children    = array();
817         $this->name        = $name;
818         $this->visiblename = $visiblename;
819         $this->hidden      = $hidden;
820     }
822     /**
823      * Returns a reference to the part_of_admin_tree object with internal name $name.
824      *
825      * @param string $name The internal name of the object we want.
826      * @param bool $findpath initialize path and visiblepath arrays
827      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
828      *                  defaults to false
829      */
830     public function locate($name, $findpath=false) {
831         if (is_array($this->category_cache) and !isset($this->category_cache[$this->name])) {
832             // somebody much have purged the cache
833             $this->category_cache[$this->name] = $this;
834         }
836         if ($this->name == $name) {
837             if ($findpath) {
838                 $this->visiblepath[] = $this->visiblename;
839                 $this->path[]        = $this->name;
840             }
841             return $this;
842         }
844         // quick category lookup
845         if (!$findpath and is_array($this->category_cache) and isset($this->category_cache[$name])) {
846             return $this->category_cache[$name];
847         }
849         $return = NULL;
850         foreach($this->children as $childid=>$unused) {
851             if ($return = $this->children[$childid]->locate($name, $findpath)) {
852                 break;
853             }
854         }
856         if (!is_null($return) and $findpath) {
857             $return->visiblepath[] = $this->visiblename;
858             $return->path[]        = $this->name;
859         }
861         return $return;
862     }
864     /**
865      * Search using query
866      *
867      * @param string query
868      * @return mixed array-object structure of found settings and pages
869      */
870     public function search($query) {
871         $result = array();
872         foreach ($this->children as $child) {
873             $subsearch = $child->search($query);
874             if (!is_array($subsearch)) {
875                 debugging('Incorrect search result from '.$child->name);
876                 continue;
877             }
878             $result = array_merge($result, $subsearch);
879         }
880         return $result;
881     }
883     /**
884      * Removes part_of_admin_tree object with internal name $name.
885      *
886      * @param string $name The internal name of the object we want to remove.
887      * @return bool success
888      */
889     public function prune($name) {
891         if ($this->name == $name) {
892             return false;  //can not remove itself
893         }
895         foreach($this->children as $precedence => $child) {
896             if ($child->name == $name) {
897                 // clear cache and delete self
898                 if (is_array($this->category_cache)) {
899                     while($this->category_cache) {
900                         // delete the cache, but keep the original array address
901                         array_pop($this->category_cache);
902                     }
903                 }
904                 unset($this->children[$precedence]);
905                 return true;
906             } else if ($this->children[$precedence]->prune($name)) {
907                 return true;
908             }
909         }
910         return false;
911     }
913     /**
914      * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
915      *
916      * @param string $destinationame The internal name of the immediate parent that we want for $something.
917      * @param mixed $something A part_of_admin_tree or setting instance to be added.
918      * @return bool True if successfully added, false if $something can not be added.
919      */
920     public function add($parentname, $something) {
921         $parent = $this->locate($parentname);
922         if (is_null($parent)) {
923             debugging('parent does not exist!');
924             return false;
925         }
927         if ($something instanceof part_of_admin_tree) {
928             if (!($parent instanceof parentable_part_of_admin_tree)) {
929                 debugging('error - parts of tree can be inserted only into parentable parts');
930                 return false;
931             }
932             $parent->children[] = $something;
933             if (is_array($this->category_cache) and ($something instanceof admin_category)) {
934                 if (isset($this->category_cache[$something->name])) {
935                     debugging('Duplicate admin category name: '.$something->name);
936                 } else {
937                     $this->category_cache[$something->name] = $something;
938                     $something->category_cache =& $this->category_cache;
939                     foreach ($something->children as $child) {
940                         // just in case somebody already added subcategories
941                         if ($child instanceof admin_category) {
942                             if (isset($this->category_cache[$child->name])) {
943                                 debugging('Duplicate admin category name: '.$child->name);
944                             } else {
945                                 $this->category_cache[$child->name] = $child;
946                                 $child->category_cache =& $this->category_cache;
947                             }
948                         }
949                     }
950                 }
951             }
952             return true;
954         } else {
955             debugging('error - can not add this element');
956             return false;
957         }
959     }
961     /**
962      * Checks if the user has access to anything in this category.
963      *
964      * @return bool True if the user has access to at least one child in this category, false otherwise.
965      */
966     public function check_access() {
967         foreach ($this->children as $child) {
968             if ($child->check_access()) {
969                 return true;
970             }
971         }
972         return false;
973     }
975     /**
976      * Is this category hidden in admin tree block?
977      *
978      * @return bool True if hidden
979      */
980     public function is_hidden() {
981         return $this->hidden;
982     }
984     /**
985      * Show we display Save button at the page bottom?
986      * @return bool
987      */
988     public function show_save() {
989         foreach ($this->children as $child) {
990             if ($child->show_save()) {
991                 return true;
992             }
993         }
994         return false;
995     }
999 /**
1000  * Root of admin settings tree, does not have any parent.
1001  *
1002  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1003  */
1004 class admin_root extends admin_category {
1005 /** @var array List of errors */
1006     public $errors;
1007     /** @var string search query */
1008     public $search;
1009     /** @var bool full tree flag - true means all settings required, false only pages required */
1010     public $fulltree;
1011     /** @var bool flag indicating loaded tree */
1012     public $loaded;
1013     /** @var mixed site custom defaults overriding defaults in settings files*/
1014     public $custom_defaults;
1016     /**
1017      * @param bool $fulltree true means all settings required,
1018      *                            false only pages required
1019      */
1020     public function __construct($fulltree) {
1021         global $CFG;
1023         parent::__construct('root', get_string('administration'), false);
1024         $this->errors   = array();
1025         $this->search   = '';
1026         $this->fulltree = $fulltree;
1027         $this->loaded   = false;
1029         $this->category_cache = array();
1031         // load custom defaults if found
1032         $this->custom_defaults = null;
1033         $defaultsfile = "$CFG->dirroot/local/defaults.php";
1034         if (is_readable($defaultsfile)) {
1035             $defaults = array();
1036             include($defaultsfile);
1037             if (is_array($defaults) and count($defaults)) {
1038                 $this->custom_defaults = $defaults;
1039             }
1040         }
1041     }
1043     /**
1044      * Empties children array, and sets loaded to false
1045      *
1046      * @param bool $requirefulltree
1047      */
1048     public function purge_children($requirefulltree) {
1049         $this->children = array();
1050         $this->fulltree = ($requirefulltree || $this->fulltree);
1051         $this->loaded   = false;
1052         //break circular dependencies - this helps PHP 5.2
1053         while($this->category_cache) {
1054             array_pop($this->category_cache);
1055         }
1056         $this->category_cache = array();
1057     }
1061 /**
1062  * Links external PHP pages into the admin tree.
1063  *
1064  * See detailed usage example at the top of this document (adminlib.php)
1065  *
1066  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1067  */
1068 class admin_externalpage implements part_of_admin_tree {
1070     /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1071     public $name;
1073     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1074     public $visiblename;
1076     /** @var string The external URL that we should link to when someone requests this external page. */
1077     public $url;
1079     /** @var string The role capability/permission a user must have to access this external page. */
1080     public $req_capability;
1082     /** @var object The context in which capability/permission should be checked, default is site context. */
1083     public $context;
1085     /** @var bool hidden in admin tree block. */
1086     public $hidden;
1088     /** @var mixed either string or array of string */
1089     public $path;
1091     /** @var array list of visible names of page parents */
1092     public $visiblepath;
1094     /**
1095      * Constructor for adding an external page into the admin tree.
1096      *
1097      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1098      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1099      * @param string $url The external URL that we should link to when someone requests this external page.
1100      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1101      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1102      * @param stdClass $context The context the page relates to. Not sure what happens
1103      *      if you specify something other than system or front page. Defaults to system.
1104      */
1105     public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1106         $this->name        = $name;
1107         $this->visiblename = $visiblename;
1108         $this->url         = $url;
1109         if (is_array($req_capability)) {
1110             $this->req_capability = $req_capability;
1111         } else {
1112             $this->req_capability = array($req_capability);
1113         }
1114         $this->hidden = $hidden;
1115         $this->context = $context;
1116     }
1118     /**
1119      * Returns a reference to the part_of_admin_tree object with internal name $name.
1120      *
1121      * @param string $name The internal name of the object we want.
1122      * @param bool $findpath defaults to false
1123      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1124      */
1125     public function locate($name, $findpath=false) {
1126         if ($this->name == $name) {
1127             if ($findpath) {
1128                 $this->visiblepath = array($this->visiblename);
1129                 $this->path        = array($this->name);
1130             }
1131             return $this;
1132         } else {
1133             $return = NULL;
1134             return $return;
1135         }
1136     }
1138     /**
1139      * This function always returns false, required function by interface
1140      *
1141      * @param string $name
1142      * @return false
1143      */
1144     public function prune($name) {
1145         return false;
1146     }
1148     /**
1149      * Search using query
1150      *
1151      * @param string $query
1152      * @return mixed array-object structure of found settings and pages
1153      */
1154     public function search($query) {
1155         $found = false;
1156         if (strpos(strtolower($this->name), $query) !== false) {
1157             $found = true;
1158         } else if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1159                 $found = true;
1160             }
1161         if ($found) {
1162             $result = new stdClass();
1163             $result->page     = $this;
1164             $result->settings = array();
1165             return array($this->name => $result);
1166         } else {
1167             return array();
1168         }
1169     }
1171     /**
1172      * Determines if the current user has access to this external page based on $this->req_capability.
1173      *
1174      * @return bool True if user has access, false otherwise.
1175      */
1176     public function check_access() {
1177         global $CFG;
1178         $context = empty($this->context) ? context_system::instance() : $this->context;
1179         foreach($this->req_capability as $cap) {
1180             if (has_capability($cap, $context)) {
1181                 return true;
1182             }
1183         }
1184         return false;
1185     }
1187     /**
1188      * Is this external page hidden in admin tree block?
1189      *
1190      * @return bool True if hidden
1191      */
1192     public function is_hidden() {
1193         return $this->hidden;
1194     }
1196     /**
1197      * Show we display Save button at the page bottom?
1198      * @return bool
1199      */
1200     public function show_save() {
1201         return false;
1202     }
1206 /**
1207  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1208  *
1209  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1210  */
1211 class admin_settingpage implements part_of_admin_tree {
1213     /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1214     public $name;
1216     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1217     public $visiblename;
1219     /** @var mixed An array of admin_setting objects that are part of this setting page. */
1220     public $settings;
1222     /** @var string The role capability/permission a user must have to access this external page. */
1223     public $req_capability;
1225     /** @var object The context in which capability/permission should be checked, default is site context. */
1226     public $context;
1228     /** @var bool hidden in admin tree block. */
1229     public $hidden;
1231     /** @var mixed string of paths or array of strings of paths */
1232     public $path;
1234     /** @var array list of visible names of page parents */
1235     public $visiblepath;
1237     /**
1238      * see admin_settingpage for details of this function
1239      *
1240      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1241      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1242      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1243      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1244      * @param stdClass $context The context the page relates to. Not sure what happens
1245      *      if you specify something other than system or front page. Defaults to system.
1246      */
1247     public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1248         $this->settings    = new stdClass();
1249         $this->name        = $name;
1250         $this->visiblename = $visiblename;
1251         if (is_array($req_capability)) {
1252             $this->req_capability = $req_capability;
1253         } else {
1254             $this->req_capability = array($req_capability);
1255         }
1256         $this->hidden      = $hidden;
1257         $this->context     = $context;
1258     }
1260     /**
1261      * see admin_category
1262      *
1263      * @param string $name
1264      * @param bool $findpath
1265      * @return mixed Object (this) if name ==  this->name, else returns null
1266      */
1267     public function locate($name, $findpath=false) {
1268         if ($this->name == $name) {
1269             if ($findpath) {
1270                 $this->visiblepath = array($this->visiblename);
1271                 $this->path        = array($this->name);
1272             }
1273             return $this;
1274         } else {
1275             $return = NULL;
1276             return $return;
1277         }
1278     }
1280     /**
1281      * Search string in settings page.
1282      *
1283      * @param string $query
1284      * @return array
1285      */
1286     public function search($query) {
1287         $found = array();
1289         foreach ($this->settings as $setting) {
1290             if ($setting->is_related($query)) {
1291                 $found[] = $setting;
1292             }
1293         }
1295         if ($found) {
1296             $result = new stdClass();
1297             $result->page     = $this;
1298             $result->settings = $found;
1299             return array($this->name => $result);
1300         }
1302         $found = false;
1303         if (strpos(strtolower($this->name), $query) !== false) {
1304             $found = true;
1305         } else if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1306                 $found = true;
1307             }
1308         if ($found) {
1309             $result = new stdClass();
1310             $result->page     = $this;
1311             $result->settings = array();
1312             return array($this->name => $result);
1313         } else {
1314             return array();
1315         }
1316     }
1318     /**
1319      * This function always returns false, required by interface
1320      *
1321      * @param string $name
1322      * @return bool Always false
1323      */
1324     public function prune($name) {
1325         return false;
1326     }
1328     /**
1329      * adds an admin_setting to this admin_settingpage
1330      *
1331      * 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
1332      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1333      *
1334      * @param object $setting is the admin_setting object you want to add
1335      * @return bool true if successful, false if not
1336      */
1337     public function add($setting) {
1338         if (!($setting instanceof admin_setting)) {
1339             debugging('error - not a setting instance');
1340             return false;
1341         }
1343         $this->settings->{$setting->name} = $setting;
1344         return true;
1345     }
1347     /**
1348      * see admin_externalpage
1349      *
1350      * @return bool Returns true for yes false for no
1351      */
1352     public function check_access() {
1353         global $CFG;
1354         $context = empty($this->context) ? context_system::instance() : $this->context;
1355         foreach($this->req_capability as $cap) {
1356             if (has_capability($cap, $context)) {
1357                 return true;
1358             }
1359         }
1360         return false;
1361     }
1363     /**
1364      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1365      * @return string Returns an XHTML string
1366      */
1367     public function output_html() {
1368         $adminroot = admin_get_root();
1369         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1370         foreach($this->settings as $setting) {
1371             $fullname = $setting->get_full_name();
1372             if (array_key_exists($fullname, $adminroot->errors)) {
1373                 $data = $adminroot->errors[$fullname]->data;
1374             } else {
1375                 $data = $setting->get_setting();
1376                 // do not use defaults if settings not available - upgrade settings handles the defaults!
1377             }
1378             $return .= $setting->output_html($data);
1379         }
1380         $return .= '</fieldset>';
1381         return $return;
1382     }
1384     /**
1385      * Is this settings page hidden in admin tree block?
1386      *
1387      * @return bool True if hidden
1388      */
1389     public function is_hidden() {
1390         return $this->hidden;
1391     }
1393     /**
1394      * Show we display Save button at the page bottom?
1395      * @return bool
1396      */
1397     public function show_save() {
1398         foreach($this->settings as $setting) {
1399             if (empty($setting->nosave)) {
1400                 return true;
1401             }
1402         }
1403         return false;
1404     }
1408 /**
1409  * Admin settings class. Only exists on setting pages.
1410  * Read & write happens at this level; no authentication.
1411  *
1412  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1413  */
1414 abstract class admin_setting {
1415     /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1416     public $name;
1417     /** @var string localised name */
1418     public $visiblename;
1419     /** @var string localised long description in Markdown format */
1420     public $description;
1421     /** @var mixed Can be string or array of string */
1422     public $defaultsetting;
1423     /** @var string */
1424     public $updatedcallback;
1425     /** @var mixed can be String or Null.  Null means main config table */
1426     public $plugin; // null means main config table
1427     /** @var bool true indicates this setting does not actually save anything, just information */
1428     public $nosave = false;
1429     /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1430     public $affectsmodinfo = false;
1432     /**
1433      * Constructor
1434      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1435      *                     or 'myplugin/mysetting' for ones in config_plugins.
1436      * @param string $visiblename localised name
1437      * @param string $description localised long description
1438      * @param mixed $defaultsetting string or array depending on implementation
1439      */
1440     public function __construct($name, $visiblename, $description, $defaultsetting) {
1441         $this->parse_setting_name($name);
1442         $this->visiblename    = $visiblename;
1443         $this->description    = $description;
1444         $this->defaultsetting = $defaultsetting;
1445     }
1447     /**
1448      * Set up $this->name and potentially $this->plugin
1449      *
1450      * Set up $this->name and possibly $this->plugin based on whether $name looks
1451      * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1452      * on the names, that is, output a developer debug warning if the name
1453      * contains anything other than [a-zA-Z0-9_]+.
1454      *
1455      * @param string $name the setting name passed in to the constructor.
1456      */
1457     private function parse_setting_name($name) {
1458         $bits = explode('/', $name);
1459         if (count($bits) > 2) {
1460             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1461         }
1462         $this->name = array_pop($bits);
1463         if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1464             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1465         }
1466         if (!empty($bits)) {
1467             $this->plugin = array_pop($bits);
1468             if ($this->plugin === 'moodle') {
1469                 $this->plugin = null;
1470             } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1471                     throw new moodle_exception('invalidadminsettingname', '', '', $name);
1472                 }
1473         }
1474     }
1476     /**
1477      * Returns the fullname prefixed by the plugin
1478      * @return string
1479      */
1480     public function get_full_name() {
1481         return 's_'.$this->plugin.'_'.$this->name;
1482     }
1484     /**
1485      * Returns the ID string based on plugin and name
1486      * @return string
1487      */
1488     public function get_id() {
1489         return 'id_s_'.$this->plugin.'_'.$this->name;
1490     }
1492     /**
1493      * @param bool $affectsmodinfo If true, changes to this setting will
1494      *   cause the course cache to be rebuilt
1495      */
1496     public function set_affects_modinfo($affectsmodinfo) {
1497         $this->affectsmodinfo = $affectsmodinfo;
1498     }
1500     /**
1501      * Returns the config if possible
1502      *
1503      * @return mixed returns config if successful else null
1504      */
1505     public function config_read($name) {
1506         global $CFG;
1507         if (!empty($this->plugin)) {
1508             $value = get_config($this->plugin, $name);
1509             return $value === false ? NULL : $value;
1511         } else {
1512             if (isset($CFG->$name)) {
1513                 return $CFG->$name;
1514             } else {
1515                 return NULL;
1516             }
1517         }
1518     }
1520     /**
1521      * Used to set a config pair and log change
1522      *
1523      * @param string $name
1524      * @param mixed $value Gets converted to string if not null
1525      * @return bool Write setting to config table
1526      */
1527     public function config_write($name, $value) {
1528         global $DB, $USER, $CFG;
1530         if ($this->nosave) {
1531             return true;
1532         }
1534         // make sure it is a real change
1535         $oldvalue = get_config($this->plugin, $name);
1536         $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1537         $value = is_null($value) ? null : (string)$value;
1539         if ($oldvalue === $value) {
1540             return true;
1541         }
1543         // store change
1544         set_config($name, $value, $this->plugin);
1546         // Some admin settings affect course modinfo
1547         if ($this->affectsmodinfo) {
1548             // Clear course cache for all courses
1549             rebuild_course_cache(0, true);
1550         }
1552         // log change
1553         $log = new stdClass();
1554         $log->userid       = during_initial_install() ? 0 :$USER->id; // 0 as user id during install
1555         $log->timemodified = time();
1556         $log->plugin       = $this->plugin;
1557         $log->name         = $name;
1558         $log->value        = $value;
1559         $log->oldvalue     = $oldvalue;
1560         $DB->insert_record('config_log', $log);
1562         return true; // BC only
1563     }
1565     /**
1566      * Returns current value of this setting
1567      * @return mixed array or string depending on instance, NULL means not set yet
1568      */
1569     public abstract function get_setting();
1571     /**
1572      * Returns default setting if exists
1573      * @return mixed array or string depending on instance; NULL means no default, user must supply
1574      */
1575     public function get_defaultsetting() {
1576         $adminroot =  admin_get_root(false, false);
1577         if (!empty($adminroot->custom_defaults)) {
1578             $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1579             if (isset($adminroot->custom_defaults[$plugin])) {
1580                 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
1581                     return $adminroot->custom_defaults[$plugin][$this->name];
1582                 }
1583             }
1584         }
1585         return $this->defaultsetting;
1586     }
1588     /**
1589      * Store new setting
1590      *
1591      * @param mixed $data string or array, must not be NULL
1592      * @return string empty string if ok, string error message otherwise
1593      */
1594     public abstract function write_setting($data);
1596     /**
1597      * Return part of form with setting
1598      * This function should always be overwritten
1599      *
1600      * @param mixed $data array or string depending on setting
1601      * @param string $query
1602      * @return string
1603      */
1604     public function output_html($data, $query='') {
1605     // should be overridden
1606         return;
1607     }
1609     /**
1610      * Function called if setting updated - cleanup, cache reset, etc.
1611      * @param string $functionname Sets the function name
1612      * @return void
1613      */
1614     public function set_updatedcallback($functionname) {
1615         $this->updatedcallback = $functionname;
1616     }
1618     /**
1619      * Is setting related to query text - used when searching
1620      * @param string $query
1621      * @return bool
1622      */
1623     public function is_related($query) {
1624         if (strpos(strtolower($this->name), $query) !== false) {
1625             return true;
1626         }
1627         if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1628             return true;
1629         }
1630         if (strpos(textlib::strtolower($this->description), $query) !== false) {
1631             return true;
1632         }
1633         $current = $this->get_setting();
1634         if (!is_null($current)) {
1635             if (is_string($current)) {
1636                 if (strpos(textlib::strtolower($current), $query) !== false) {
1637                     return true;
1638                 }
1639             }
1640         }
1641         $default = $this->get_defaultsetting();
1642         if (!is_null($default)) {
1643             if (is_string($default)) {
1644                 if (strpos(textlib::strtolower($default), $query) !== false) {
1645                     return true;
1646                 }
1647             }
1648         }
1649         return false;
1650     }
1654 /**
1655  * No setting - just heading and text.
1656  *
1657  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1658  */
1659 class admin_setting_heading extends admin_setting {
1661     /**
1662      * not a setting, just text
1663      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1664      * @param string $heading heading
1665      * @param string $information text in box
1666      */
1667     public function __construct($name, $heading, $information) {
1668         $this->nosave = true;
1669         parent::__construct($name, $heading, $information, '');
1670     }
1672     /**
1673      * Always returns true
1674      * @return bool Always returns true
1675      */
1676     public function get_setting() {
1677         return true;
1678     }
1680     /**
1681      * Always returns true
1682      * @return bool Always returns true
1683      */
1684     public function get_defaultsetting() {
1685         return true;
1686     }
1688     /**
1689      * Never write settings
1690      * @return string Always returns an empty string
1691      */
1692     public function write_setting($data) {
1693     // do not write any setting
1694         return '';
1695     }
1697     /**
1698      * Returns an HTML string
1699      * @return string Returns an HTML string
1700      */
1701     public function output_html($data, $query='') {
1702         global $OUTPUT;
1703         $return = '';
1704         if ($this->visiblename != '') {
1705             $return .= $OUTPUT->heading($this->visiblename, 3, 'main');
1706         }
1707         if ($this->description != '') {
1708             $return .= $OUTPUT->box(highlight($query, markdown_to_html($this->description)), 'generalbox formsettingheading');
1709         }
1710         return $return;
1711     }
1715 /**
1716  * The most flexibly setting, user is typing text
1717  *
1718  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1719  */
1720 class admin_setting_configtext extends admin_setting {
1722     /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
1723     public $paramtype;
1724     /** @var int default field size */
1725     public $size;
1727     /**
1728      * Config text constructor
1729      *
1730      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1731      * @param string $visiblename localised
1732      * @param string $description long localised info
1733      * @param string $defaultsetting
1734      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
1735      * @param int $size default field size
1736      */
1737     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
1738         $this->paramtype = $paramtype;
1739         if (!is_null($size)) {
1740             $this->size  = $size;
1741         } else {
1742             $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
1743         }
1744         parent::__construct($name, $visiblename, $description, $defaultsetting);
1745     }
1747     /**
1748      * Return the setting
1749      *
1750      * @return mixed returns config if successful else null
1751      */
1752     public function get_setting() {
1753         return $this->config_read($this->name);
1754     }
1756     public function write_setting($data) {
1757         if ($this->paramtype === PARAM_INT and $data === '') {
1758         // do not complain if '' used instead of 0
1759             $data = 0;
1760         }
1761         // $data is a string
1762         $validated = $this->validate($data);
1763         if ($validated !== true) {
1764             return $validated;
1765         }
1766         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1767     }
1769     /**
1770      * Validate data before storage
1771      * @param string data
1772      * @return mixed true if ok string if error found
1773      */
1774     public function validate($data) {
1775         // allow paramtype to be a custom regex if it is the form of /pattern/
1776         if (preg_match('#^/.*/$#', $this->paramtype)) {
1777             if (preg_match($this->paramtype, $data)) {
1778                 return true;
1779             } else {
1780                 return get_string('validateerror', 'admin');
1781             }
1783         } else if ($this->paramtype === PARAM_RAW) {
1784             return true;
1786         } else {
1787             $cleaned = clean_param($data, $this->paramtype);
1788             if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
1789                 return true;
1790             } else {
1791                 return get_string('validateerror', 'admin');
1792             }
1793         }
1794     }
1796     /**
1797      * Return an XHTML string for the setting
1798      * @return string Returns an XHTML string
1799      */
1800     public function output_html($data, $query='') {
1801         $default = $this->get_defaultsetting();
1803         return format_admin_setting($this, $this->visiblename,
1804         '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
1805         $this->description, true, '', $default, $query);
1806     }
1810 /**
1811  * General text area without html editor.
1812  *
1813  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1814  */
1815 class admin_setting_configtextarea extends admin_setting_configtext {
1816     private $rows;
1817     private $cols;
1819     /**
1820      * @param string $name
1821      * @param string $visiblename
1822      * @param string $description
1823      * @param mixed $defaultsetting string or array
1824      * @param mixed $paramtype
1825      * @param string $cols The number of columns to make the editor
1826      * @param string $rows The number of rows to make the editor
1827      */
1828     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1829         $this->rows = $rows;
1830         $this->cols = $cols;
1831         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1832     }
1834     /**
1835      * Returns an XHTML string for the editor
1836      *
1837      * @param string $data
1838      * @param string $query
1839      * @return string XHTML string for the editor
1840      */
1841     public function output_html($data, $query='') {
1842         $default = $this->get_defaultsetting();
1844         $defaultinfo = $default;
1845         if (!is_null($default) and $default !== '') {
1846             $defaultinfo = "\n".$default;
1847         }
1849         return format_admin_setting($this, $this->visiblename,
1850         '<div class="form-textarea" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1851         $this->description, true, '', $defaultinfo, $query);
1852     }
1856 /**
1857  * General text area with html editor.
1858  */
1859 class admin_setting_confightmleditor extends admin_setting_configtext {
1860     private $rows;
1861     private $cols;
1863     /**
1864      * @param string $name
1865      * @param string $visiblename
1866      * @param string $description
1867      * @param mixed $defaultsetting string or array
1868      * @param mixed $paramtype
1869      */
1870     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1871         $this->rows = $rows;
1872         $this->cols = $cols;
1873         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1874         editors_head_setup();
1875     }
1877     /**
1878      * Returns an XHTML string for the editor
1879      *
1880      * @param string $data
1881      * @param string $query
1882      * @return string XHTML string for the editor
1883      */
1884     public function output_html($data, $query='') {
1885         $default = $this->get_defaultsetting();
1887         $defaultinfo = $default;
1888         if (!is_null($default) and $default !== '') {
1889             $defaultinfo = "\n".$default;
1890         }
1892         $editor = editors_get_preferred_editor(FORMAT_HTML);
1893         $editor->use_editor($this->get_id(), array('noclean'=>true));
1895         return format_admin_setting($this, $this->visiblename,
1896         '<div class="form-textarea"><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1897         $this->description, true, '', $defaultinfo, $query);
1898     }
1902 /**
1903  * Password field, allows unmasking of password
1904  *
1905  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1906  */
1907 class admin_setting_configpasswordunmask extends admin_setting_configtext {
1908     /**
1909      * Constructor
1910      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1911      * @param string $visiblename localised
1912      * @param string $description long localised info
1913      * @param string $defaultsetting default password
1914      */
1915     public function __construct($name, $visiblename, $description, $defaultsetting) {
1916         parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
1917     }
1919     /**
1920      * Returns XHTML for the field
1921      * Writes Javascript into the HTML below right before the last div
1922      *
1923      * @todo Make javascript available through newer methods if possible
1924      * @param string $data Value for the field
1925      * @param string $query Passed as final argument for format_admin_setting
1926      * @return string XHTML field
1927      */
1928     public function output_html($data, $query='') {
1929         $id = $this->get_id();
1930         $unmask = get_string('unmaskpassword', 'form');
1931         $unmaskjs = '<script type="text/javascript">
1932 //<![CDATA[
1933 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
1935 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
1937 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
1939 var unmaskchb = document.createElement("input");
1940 unmaskchb.setAttribute("type", "checkbox");
1941 unmaskchb.setAttribute("id", "'.$id.'unmask");
1942 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
1943 unmaskdiv.appendChild(unmaskchb);
1945 var unmasklbl = document.createElement("label");
1946 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
1947 if (is_ie) {
1948   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
1949 } else {
1950   unmasklbl.setAttribute("for", "'.$id.'unmask");
1952 unmaskdiv.appendChild(unmasklbl);
1954 if (is_ie) {
1955   // ugly hack to work around the famous onchange IE bug
1956   unmaskchb.onclick = function() {this.blur();};
1957   unmaskdiv.onclick = function() {this.blur();};
1959 //]]>
1960 </script>';
1961         return format_admin_setting($this, $this->visiblename,
1962         '<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>',
1963         $this->description, true, '', NULL, $query);
1964     }
1968 /**
1969  * Path to directory
1970  *
1971  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1972  */
1973 class admin_setting_configfile extends admin_setting_configtext {
1974     /**
1975      * Constructor
1976      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1977      * @param string $visiblename localised
1978      * @param string $description long localised info
1979      * @param string $defaultdirectory default directory location
1980      */
1981     public function __construct($name, $visiblename, $description, $defaultdirectory) {
1982         parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
1983     }
1985     /**
1986      * Returns XHTML for the field
1987      *
1988      * Returns XHTML for the field and also checks whether the file
1989      * specified in $data exists using file_exists()
1990      *
1991      * @param string $data File name and path to use in value attr
1992      * @param string $query
1993      * @return string XHTML field
1994      */
1995     public function output_html($data, $query='') {
1996         $default = $this->get_defaultsetting();
1998         if ($data) {
1999             if (file_exists($data)) {
2000                 $executable = '<span class="pathok">&#x2714;</span>';
2001             } else {
2002                 $executable = '<span class="patherror">&#x2718;</span>';
2003             }
2004         } else {
2005             $executable = '';
2006         }
2008         return format_admin_setting($this, $this->visiblename,
2009         '<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>',
2010         $this->description, true, '', $default, $query);
2011     }
2015 /**
2016  * Path to executable file
2017  *
2018  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2019  */
2020 class admin_setting_configexecutable extends admin_setting_configfile {
2022     /**
2023      * Returns an XHTML field
2024      *
2025      * @param string $data This is the value for the field
2026      * @param string $query
2027      * @return string XHTML field
2028      */
2029     public function output_html($data, $query='') {
2030         $default = $this->get_defaultsetting();
2032         if ($data) {
2033             if (file_exists($data) and is_executable($data)) {
2034                 $executable = '<span class="pathok">&#x2714;</span>';
2035             } else {
2036                 $executable = '<span class="patherror">&#x2718;</span>';
2037             }
2038         } else {
2039             $executable = '';
2040         }
2042         return format_admin_setting($this, $this->visiblename,
2043         '<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>',
2044         $this->description, true, '', $default, $query);
2045     }
2049 /**
2050  * Path to directory
2051  *
2052  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2053  */
2054 class admin_setting_configdirectory extends admin_setting_configfile {
2056     /**
2057      * Returns an XHTML field
2058      *
2059      * @param string $data This is the value for the field
2060      * @param string $query
2061      * @return string XHTML
2062      */
2063     public function output_html($data, $query='') {
2064         $default = $this->get_defaultsetting();
2066         if ($data) {
2067             if (file_exists($data) and is_dir($data)) {
2068                 $executable = '<span class="pathok">&#x2714;</span>';
2069             } else {
2070                 $executable = '<span class="patherror">&#x2718;</span>';
2071             }
2072         } else {
2073             $executable = '';
2074         }
2076         return format_admin_setting($this, $this->visiblename,
2077         '<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>',
2078         $this->description, true, '', $default, $query);
2079     }
2083 /**
2084  * Checkbox
2085  *
2086  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2087  */
2088 class admin_setting_configcheckbox extends admin_setting {
2089     /** @var string Value used when checked */
2090     public $yes;
2091     /** @var string Value used when not checked */
2092     public $no;
2094     /**
2095      * Constructor
2096      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2097      * @param string $visiblename localised
2098      * @param string $description long localised info
2099      * @param string $defaultsetting
2100      * @param string $yes value used when checked
2101      * @param string $no value used when not checked
2102      */
2103     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2104         parent::__construct($name, $visiblename, $description, $defaultsetting);
2105         $this->yes = (string)$yes;
2106         $this->no  = (string)$no;
2107     }
2109     /**
2110      * Retrieves the current setting using the objects name
2111      *
2112      * @return string
2113      */
2114     public function get_setting() {
2115         return $this->config_read($this->name);
2116     }
2118     /**
2119      * Sets the value for the setting
2120      *
2121      * Sets the value for the setting to either the yes or no values
2122      * of the object by comparing $data to yes
2123      *
2124      * @param mixed $data Gets converted to str for comparison against yes value
2125      * @return string empty string or error
2126      */
2127     public function write_setting($data) {
2128         if ((string)$data === $this->yes) { // convert to strings before comparison
2129             $data = $this->yes;
2130         } else {
2131             $data = $this->no;
2132         }
2133         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2134     }
2136     /**
2137      * Returns an XHTML checkbox field
2138      *
2139      * @param string $data If $data matches yes then checkbox is checked
2140      * @param string $query
2141      * @return string XHTML field
2142      */
2143     public function output_html($data, $query='') {
2144         $default = $this->get_defaultsetting();
2146         if (!is_null($default)) {
2147             if ((string)$default === $this->yes) {
2148                 $defaultinfo = get_string('checkboxyes', 'admin');
2149             } else {
2150                 $defaultinfo = get_string('checkboxno', 'admin');
2151             }
2152         } else {
2153             $defaultinfo = NULL;
2154         }
2156         if ((string)$data === $this->yes) { // convert to strings before comparison
2157             $checked = 'checked="checked"';
2158         } else {
2159             $checked = '';
2160         }
2162         return format_admin_setting($this, $this->visiblename,
2163         '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2164             .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2165         $this->description, true, '', $defaultinfo, $query);
2166     }
2170 /**
2171  * Multiple checkboxes, each represents different value, stored in csv format
2172  *
2173  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2174  */
2175 class admin_setting_configmulticheckbox extends admin_setting {
2176     /** @var array Array of choices value=>label */
2177     public $choices;
2179     /**
2180      * Constructor: uses parent::__construct
2181      *
2182      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2183      * @param string $visiblename localised
2184      * @param string $description long localised info
2185      * @param array $defaultsetting array of selected
2186      * @param array $choices array of $value=>$label for each checkbox
2187      */
2188     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2189         $this->choices = $choices;
2190         parent::__construct($name, $visiblename, $description, $defaultsetting);
2191     }
2193     /**
2194      * This public function may be used in ancestors for lazy loading of choices
2195      *
2196      * @todo Check if this function is still required content commented out only returns true
2197      * @return bool true if loaded, false if error
2198      */
2199     public function load_choices() {
2200         /*
2201         if (is_array($this->choices)) {
2202             return true;
2203         }
2204         .... load choices here
2205         */
2206         return true;
2207     }
2209     /**
2210      * Is setting related to query text - used when searching
2211      *
2212      * @param string $query
2213      * @return bool true on related, false on not or failure
2214      */
2215     public function is_related($query) {
2216         if (!$this->load_choices() or empty($this->choices)) {
2217             return false;
2218         }
2219         if (parent::is_related($query)) {
2220             return true;
2221         }
2223         foreach ($this->choices as $desc) {
2224             if (strpos(textlib::strtolower($desc), $query) !== false) {
2225                 return true;
2226             }
2227         }
2228         return false;
2229     }
2231     /**
2232      * Returns the current setting if it is set
2233      *
2234      * @return mixed null if null, else an array
2235      */
2236     public function get_setting() {
2237         $result = $this->config_read($this->name);
2239         if (is_null($result)) {
2240             return NULL;
2241         }
2242         if ($result === '') {
2243             return array();
2244         }
2245         $enabled = explode(',', $result);
2246         $setting = array();
2247         foreach ($enabled as $option) {
2248             $setting[$option] = 1;
2249         }
2250         return $setting;
2251     }
2253     /**
2254      * Saves the setting(s) provided in $data
2255      *
2256      * @param array $data An array of data, if not array returns empty str
2257      * @return mixed empty string on useless data or bool true=success, false=failed
2258      */
2259     public function write_setting($data) {
2260         if (!is_array($data)) {
2261             return ''; // ignore it
2262         }
2263         if (!$this->load_choices() or empty($this->choices)) {
2264             return '';
2265         }
2266         unset($data['xxxxx']);
2267         $result = array();
2268         foreach ($data as $key => $value) {
2269             if ($value and array_key_exists($key, $this->choices)) {
2270                 $result[] = $key;
2271             }
2272         }
2273         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2274     }
2276     /**
2277      * Returns XHTML field(s) as required by choices
2278      *
2279      * Relies on data being an array should data ever be another valid vartype with
2280      * acceptable value this may cause a warning/error
2281      * if (!is_array($data)) would fix the problem
2282      *
2283      * @todo Add vartype handling to ensure $data is an array
2284      *
2285      * @param array $data An array of checked values
2286      * @param string $query
2287      * @return string XHTML field
2288      */
2289     public function output_html($data, $query='') {
2290         if (!$this->load_choices() or empty($this->choices)) {
2291             return '';
2292         }
2293         $default = $this->get_defaultsetting();
2294         if (is_null($default)) {
2295             $default = array();
2296         }
2297         if (is_null($data)) {
2298             $data = array();
2299         }
2300         $options = array();
2301         $defaults = array();
2302         foreach ($this->choices as $key=>$description) {
2303             if (!empty($data[$key])) {
2304                 $checked = 'checked="checked"';
2305             } else {
2306                 $checked = '';
2307             }
2308             if (!empty($default[$key])) {
2309                 $defaults[] = $description;
2310             }
2312             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
2313                 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
2314         }
2316         if (is_null($default)) {
2317             $defaultinfo = NULL;
2318         } else if (!empty($defaults)) {
2319                 $defaultinfo = implode(', ', $defaults);
2320             } else {
2321                 $defaultinfo = get_string('none');
2322             }
2324         $return = '<div class="form-multicheckbox">';
2325         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2326         if ($options) {
2327             $return .= '<ul>';
2328             foreach ($options as $option) {
2329                 $return .= '<li>'.$option.'</li>';
2330             }
2331             $return .= '</ul>';
2332         }
2333         $return .= '</div>';
2335         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2337     }
2341 /**
2342  * Multiple checkboxes 2, value stored as string 00101011
2343  *
2344  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2345  */
2346 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
2348     /**
2349      * Returns the setting if set
2350      *
2351      * @return mixed null if not set, else an array of set settings
2352      */
2353     public function get_setting() {
2354         $result = $this->config_read($this->name);
2355         if (is_null($result)) {
2356             return NULL;
2357         }
2358         if (!$this->load_choices()) {
2359             return NULL;
2360         }
2361         $result = str_pad($result, count($this->choices), '0');
2362         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2363         $setting = array();
2364         foreach ($this->choices as $key=>$unused) {
2365             $value = array_shift($result);
2366             if ($value) {
2367                 $setting[$key] = 1;
2368             }
2369         }
2370         return $setting;
2371     }
2373     /**
2374      * Save setting(s) provided in $data param
2375      *
2376      * @param array $data An array of settings to save
2377      * @return mixed empty string for bad data or bool true=>success, false=>error
2378      */
2379     public function write_setting($data) {
2380         if (!is_array($data)) {
2381             return ''; // ignore it
2382         }
2383         if (!$this->load_choices() or empty($this->choices)) {
2384             return '';
2385         }
2386         $result = '';
2387         foreach ($this->choices as $key=>$unused) {
2388             if (!empty($data[$key])) {
2389                 $result .= '1';
2390             } else {
2391                 $result .= '0';
2392             }
2393         }
2394         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2395     }
2399 /**
2400  * Select one value from list
2401  *
2402  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2403  */
2404 class admin_setting_configselect extends admin_setting {
2405     /** @var array Array of choices value=>label */
2406     public $choices;
2408     /**
2409      * Constructor
2410      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2411      * @param string $visiblename localised
2412      * @param string $description long localised info
2413      * @param string|int $defaultsetting
2414      * @param array $choices array of $value=>$label for each selection
2415      */
2416     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2417         $this->choices = $choices;
2418         parent::__construct($name, $visiblename, $description, $defaultsetting);
2419     }
2421     /**
2422      * This function may be used in ancestors for lazy loading of choices
2423      *
2424      * Override this method if loading of choices is expensive, such
2425      * as when it requires multiple db requests.
2426      *
2427      * @return bool true if loaded, false if error
2428      */
2429     public function load_choices() {
2430         /*
2431         if (is_array($this->choices)) {
2432             return true;
2433         }
2434         .... load choices here
2435         */
2436         return true;
2437     }
2439     /**
2440      * Check if this is $query is related to a choice
2441      *
2442      * @param string $query
2443      * @return bool true if related, false if not
2444      */
2445     public function is_related($query) {
2446         if (parent::is_related($query)) {
2447             return true;
2448         }
2449         if (!$this->load_choices()) {
2450             return false;
2451         }
2452         foreach ($this->choices as $key=>$value) {
2453             if (strpos(textlib::strtolower($key), $query) !== false) {
2454                 return true;
2455             }
2456             if (strpos(textlib::strtolower($value), $query) !== false) {
2457                 return true;
2458             }
2459         }
2460         return false;
2461     }
2463     /**
2464      * Return the setting
2465      *
2466      * @return mixed returns config if successful else null
2467      */
2468     public function get_setting() {
2469         return $this->config_read($this->name);
2470     }
2472     /**
2473      * Save a setting
2474      *
2475      * @param string $data
2476      * @return string empty of error string
2477      */
2478     public function write_setting($data) {
2479         if (!$this->load_choices() or empty($this->choices)) {
2480             return '';
2481         }
2482         if (!array_key_exists($data, $this->choices)) {
2483             return ''; // ignore it
2484         }
2486         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2487     }
2489     /**
2490      * Returns XHTML select field
2491      *
2492      * Ensure the options are loaded, and generate the XHTML for the select
2493      * element and any warning message. Separating this out from output_html
2494      * makes it easier to subclass this class.
2495      *
2496      * @param string $data the option to show as selected.
2497      * @param string $current the currently selected option in the database, null if none.
2498      * @param string $default the default selected option.
2499      * @return array the HTML for the select element, and a warning message.
2500      */
2501     public function output_select_html($data, $current, $default, $extraname = '') {
2502         if (!$this->load_choices() or empty($this->choices)) {
2503             return array('', '');
2504         }
2506         $warning = '';
2507         if (is_null($current)) {
2508         // first run
2509         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2510             // no warning
2511             } else if (!array_key_exists($current, $this->choices)) {
2512                     $warning = get_string('warningcurrentsetting', 'admin', s($current));
2513                     if (!is_null($default) and $data == $current) {
2514                         $data = $default; // use default instead of first value when showing the form
2515                     }
2516                 }
2518         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
2519         foreach ($this->choices as $key => $value) {
2520         // the string cast is needed because key may be integer - 0 is equal to most strings!
2521             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
2522         }
2523         $selecthtml .= '</select>';
2524         return array($selecthtml, $warning);
2525     }
2527     /**
2528      * Returns XHTML select field and wrapping div(s)
2529      *
2530      * @see output_select_html()
2531      *
2532      * @param string $data the option to show as selected
2533      * @param string $query
2534      * @return string XHTML field and wrapping div
2535      */
2536     public function output_html($data, $query='') {
2537         $default = $this->get_defaultsetting();
2538         $current = $this->get_setting();
2540         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2541         if (!$selecthtml) {
2542             return '';
2543         }
2545         if (!is_null($default) and array_key_exists($default, $this->choices)) {
2546             $defaultinfo = $this->choices[$default];
2547         } else {
2548             $defaultinfo = NULL;
2549         }
2551         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
2553         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
2554     }
2558 /**
2559  * Select multiple items from list
2560  *
2561  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2562  */
2563 class admin_setting_configmultiselect extends admin_setting_configselect {
2564     /**
2565      * Constructor
2566      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2567      * @param string $visiblename localised
2568      * @param string $description long localised info
2569      * @param array $defaultsetting array of selected items
2570      * @param array $choices array of $value=>$label for each list item
2571      */
2572     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2573         parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
2574     }
2576     /**
2577      * Returns the select setting(s)
2578      *
2579      * @return mixed null or array. Null if no settings else array of setting(s)
2580      */
2581     public function get_setting() {
2582         $result = $this->config_read($this->name);
2583         if (is_null($result)) {
2584             return NULL;
2585         }
2586         if ($result === '') {
2587             return array();
2588         }
2589         return explode(',', $result);
2590     }
2592     /**
2593      * Saves setting(s) provided through $data
2594      *
2595      * Potential bug in the works should anyone call with this function
2596      * using a vartype that is not an array
2597      *
2598      * @param array $data
2599      */
2600     public function write_setting($data) {
2601         if (!is_array($data)) {
2602             return ''; //ignore it
2603         }
2604         if (!$this->load_choices() or empty($this->choices)) {
2605             return '';
2606         }
2608         unset($data['xxxxx']);
2610         $save = array();
2611         foreach ($data as $value) {
2612             if (!array_key_exists($value, $this->choices)) {
2613                 continue; // ignore it
2614             }
2615             $save[] = $value;
2616         }
2618         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2619     }
2621     /**
2622      * Is setting related to query text - used when searching
2623      *
2624      * @param string $query
2625      * @return bool true if related, false if not
2626      */
2627     public function is_related($query) {
2628         if (!$this->load_choices() or empty($this->choices)) {
2629             return false;
2630         }
2631         if (parent::is_related($query)) {
2632             return true;
2633         }
2635         foreach ($this->choices as $desc) {
2636             if (strpos(textlib::strtolower($desc), $query) !== false) {
2637                 return true;
2638             }
2639         }
2640         return false;
2641     }
2643     /**
2644      * Returns XHTML multi-select field
2645      *
2646      * @todo Add vartype handling to ensure $data is an array
2647      * @param array $data Array of values to select by default
2648      * @param string $query
2649      * @return string XHTML multi-select field
2650      */
2651     public function output_html($data, $query='') {
2652         if (!$this->load_choices() or empty($this->choices)) {
2653             return '';
2654         }
2655         $choices = $this->choices;
2656         $default = $this->get_defaultsetting();
2657         if (is_null($default)) {
2658             $default = array();
2659         }
2660         if (is_null($data)) {
2661             $data = array();
2662         }
2664         $defaults = array();
2665         $size = min(10, count($this->choices));
2666         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2667         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
2668         foreach ($this->choices as $key => $description) {
2669             if (in_array($key, $data)) {
2670                 $selected = 'selected="selected"';
2671             } else {
2672                 $selected = '';
2673             }
2674             if (in_array($key, $default)) {
2675                 $defaults[] = $description;
2676             }
2678             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2679         }
2681         if (is_null($default)) {
2682             $defaultinfo = NULL;
2683         } if (!empty($defaults)) {
2684             $defaultinfo = implode(', ', $defaults);
2685         } else {
2686             $defaultinfo = get_string('none');
2687         }
2689         $return .= '</select></div>';
2690         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
2691     }
2694 /**
2695  * Time selector
2696  *
2697  * This is a liiitle bit messy. we're using two selects, but we're returning
2698  * them as an array named after $name (so we only use $name2 internally for the setting)
2699  *
2700  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2701  */
2702 class admin_setting_configtime extends admin_setting {
2703     /** @var string Used for setting second select (minutes) */
2704     public $name2;
2706     /**
2707      * Constructor
2708      * @param string $hoursname setting for hours
2709      * @param string $minutesname setting for hours
2710      * @param string $visiblename localised
2711      * @param string $description long localised info
2712      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2713      */
2714     public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
2715         $this->name2 = $minutesname;
2716         parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
2717     }
2719     /**
2720      * Get the selected time
2721      *
2722      * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
2723      */
2724     public function get_setting() {
2725         $result1 = $this->config_read($this->name);
2726         $result2 = $this->config_read($this->name2);
2727         if (is_null($result1) or is_null($result2)) {
2728             return NULL;
2729         }
2731         return array('h' => $result1, 'm' => $result2);
2732     }
2734     /**
2735      * Store the time (hours and minutes)
2736      *
2737      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2738      * @return bool true if success, false if not
2739      */
2740     public function write_setting($data) {
2741         if (!is_array($data)) {
2742             return '';
2743         }
2745         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2746         return ($result ? '' : get_string('errorsetting', 'admin'));
2747     }
2749     /**
2750      * Returns XHTML time select fields
2751      *
2752      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2753      * @param string $query
2754      * @return string XHTML time select fields and wrapping div(s)
2755      */
2756     public function output_html($data, $query='') {
2757         $default = $this->get_defaultsetting();
2759         if (is_array($default)) {
2760             $defaultinfo = $default['h'].':'.$default['m'];
2761         } else {
2762             $defaultinfo = NULL;
2763         }
2765         $return = '<div class="form-time defaultsnext">'.
2766             '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
2767         for ($i = 0; $i < 24; $i++) {
2768             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2769         }
2770         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2771         for ($i = 0; $i < 60; $i += 5) {
2772             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2773         }
2774         $return .= '</select></div>';
2775         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2776     }
2781 /**
2782  * Seconds duration setting.
2783  *
2784  * @copyright 2012 Petr Skoda (http://skodak.org)
2785  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2786  */
2787 class admin_setting_configduration extends admin_setting {
2789     /** @var int default duration unit */
2790     protected $defaultunit;
2792     /**
2793      * Constructor
2794      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2795      *                     or 'myplugin/mysetting' for ones in config_plugins.
2796      * @param string $visiblename localised name
2797      * @param string $description localised long description
2798      * @param mixed $defaultsetting string or array depending on implementation
2799      * @param int $defaultunit - day, week, etc. (in seconds)
2800      */
2801     public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
2802         if (is_number($defaultsetting)) {
2803             $defaultsetting = self::parse_seconds($defaultsetting);
2804         }
2805         $units = self::get_units();
2806         if (isset($units[$defaultunit])) {
2807             $this->defaultunit = $defaultunit;
2808         } else {
2809             $this->defaultunit = 86400;
2810         }
2811         parent::__construct($name, $visiblename, $description, $defaultsetting);
2812     }
2814     /**
2815      * Returns selectable units.
2816      * @static
2817      * @return array
2818      */
2819     protected static function get_units() {
2820         return array(
2821             604800 => get_string('weeks'),
2822             86400 => get_string('days'),
2823             3600 => get_string('hours'),
2824             60 => get_string('minutes'),
2825             1 => get_string('seconds'),
2826         );
2827     }
2829     /**
2830      * Converts seconds to some more user friendly string.
2831      * @static
2832      * @param int $seconds
2833      * @return string
2834      */
2835     protected static function get_duration_text($seconds) {
2836         if (empty($seconds)) {
2837             return get_string('none');
2838         }
2839         $data = self::parse_seconds($seconds);
2840         switch ($data['u']) {
2841             case (60*60*24*7):
2842                 return get_string('numweeks', '', $data['v']);
2843             case (60*60*24):
2844                 return get_string('numdays', '', $data['v']);
2845             case (60*60):
2846                 return get_string('numhours', '', $data['v']);
2847             case (60):
2848                 return get_string('numminutes', '', $data['v']);
2849             default:
2850                 return get_string('numseconds', '', $data['v']*$data['u']);
2851         }
2852     }
2854     /**
2855      * Finds suitable units for given duration.
2856      * @static
2857      * @param int $seconds
2858      * @return array
2859      */
2860     protected static function parse_seconds($seconds) {
2861         foreach (self::get_units() as $unit => $unused) {
2862             if ($seconds % $unit === 0) {
2863                 return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
2864             }
2865         }
2866         return array('v'=>(int)$seconds, 'u'=>1);
2867     }
2869     /**
2870      * Get the selected duration as array.
2871      *
2872      * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
2873      */
2874     public function get_setting() {
2875         $seconds = $this->config_read($this->name);
2876         if (is_null($seconds)) {
2877             return null;
2878         }
2880         return self::parse_seconds($seconds);
2881     }
2883     /**
2884      * Store the duration as seconds.
2885      *
2886      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2887      * @return bool true if success, false if not
2888      */
2889     public function write_setting($data) {
2890         if (!is_array($data)) {
2891             return '';
2892         }
2894         $seconds = (int)($data['v']*$data['u']);
2895         if ($seconds < 0) {
2896             return get_string('errorsetting', 'admin');
2897         }
2899         $result = $this->config_write($this->name, $seconds);
2900         return ($result ? '' : get_string('errorsetting', 'admin'));
2901     }
2903     /**
2904      * Returns duration text+select fields.
2905      *
2906      * @param array $data Must be form 'v'=>xx, 'u'=>xx
2907      * @param string $query
2908      * @return string duration text+select fields and wrapping div(s)
2909      */
2910     public function output_html($data, $query='') {
2911         $default = $this->get_defaultsetting();
2913         if (is_number($default)) {
2914             $defaultinfo = self::get_duration_text($default);
2915         } else if (is_array($default)) {
2916             $defaultinfo = self::get_duration_text($default['v']*$default['u']);
2917         } else {
2918             $defaultinfo = null;
2919         }
2921         $units = self::get_units();
2923         $return = '<div class="form-duration defaultsnext">';
2924         $return .= '<input type="text" size="5" id="'.$this->get_id().'v" name="'.$this->get_full_name().'[v]" value="'.s($data['v']).'" />';
2925         $return .= '<select id="'.$this->get_id().'u" name="'.$this->get_full_name().'[u]">';
2926         foreach ($units as $val => $text) {
2927             $selected = '';
2928             if ($data['v'] == 0) {
2929                 if ($val == $this->defaultunit) {
2930                     $selected = ' selected="selected"';
2931                 }
2932             } else if ($val == $data['u']) {
2933                 $selected = ' selected="selected"';
2934             }
2935             $return .= '<option value="'.$val.'"'.$selected.'>'.$text.'</option>';
2936         }
2937         $return .= '</select></div>';
2938         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2939     }
2943 /**
2944  * Used to validate a textarea used for ip addresses
2945  *
2946  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2947  */
2948 class admin_setting_configiplist extends admin_setting_configtextarea {
2950     /**
2951      * Validate the contents of the textarea as IP addresses
2952      *
2953      * Used to validate a new line separated list of IP addresses collected from
2954      * a textarea control
2955      *
2956      * @param string $data A list of IP Addresses separated by new lines
2957      * @return mixed bool true for success or string:error on failure
2958      */
2959     public function validate($data) {
2960         if(!empty($data)) {
2961             $ips = explode("\n", $data);
2962         } else {
2963             return true;
2964         }
2965         $result = true;
2966         foreach($ips as $ip) {
2967             $ip = trim($ip);
2968             if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
2969                 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
2970                 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
2971                 $result = true;
2972             } else {
2973                 $result = false;
2974                 break;
2975             }
2976         }
2977         if($result) {
2978             return true;
2979         } else {
2980             return get_string('validateerror', 'admin');
2981         }
2982     }
2986 /**
2987  * An admin setting for selecting one or more users who have a capability
2988  * in the system context
2989  *
2990  * An admin setting for selecting one or more users, who have a particular capability
2991  * in the system context. Warning, make sure the list will never be too long. There is
2992  * no paging or searching of this list.
2993  *
2994  * To correctly get a list of users from this config setting, you need to call the
2995  * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
2996  *
2997  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2998  */
2999 class admin_setting_users_with_capability extends admin_setting_configmultiselect {
3000     /** @var string The capabilities name */
3001     protected $capability;
3002     /** @var int include admin users too */
3003     protected $includeadmins;
3005     /**
3006      * Constructor.
3007      *
3008      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3009      * @param string $visiblename localised name
3010      * @param string $description localised long description
3011      * @param array $defaultsetting array of usernames
3012      * @param string $capability string capability name.
3013      * @param bool $includeadmins include administrators
3014      */
3015     function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
3016         $this->capability    = $capability;
3017         $this->includeadmins = $includeadmins;
3018         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
3019     }
3021     /**
3022      * Load all of the uses who have the capability into choice array
3023      *
3024      * @return bool Always returns true
3025      */
3026     function load_choices() {
3027         if (is_array($this->choices)) {
3028             return true;
3029         }
3030         list($sort, $sortparams) = users_order_by_sql('u');
3031         if (!empty($sortparams)) {
3032             throw new coding_exception('users_order_by_sql returned some query parameters. ' .
3033                     'This is unexpected, and a problem because there is no way to pass these ' .
3034                     'parameters to get_users_by_capability. See MDL-34657.');
3035         }
3036         $users = get_users_by_capability(context_system::instance(),
3037                 $this->capability, 'u.id,u.username,u.firstname,u.lastname', $sort);
3038         $this->choices = array(
3039             '$@NONE@$' => get_string('nobody'),
3040             '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
3041         );
3042         if ($this->includeadmins) {
3043             $admins = get_admins();
3044             foreach ($admins as $user) {
3045                 $this->choices[$user->id] = fullname($user);
3046             }
3047         }
3048         if (is_array($users)) {
3049             foreach ($users as $user) {
3050                 $this->choices[$user->id] = fullname($user);
3051             }
3052         }
3053         return true;
3054     }
3056     /**
3057      * Returns the default setting for class
3058      *
3059      * @return mixed Array, or string. Empty string if no default
3060      */
3061     public function get_defaultsetting() {
3062         $this->load_choices();
3063         $defaultsetting = parent::get_defaultsetting();
3064         if (empty($defaultsetting)) {
3065             return array('$@NONE@$');
3066         } else if (array_key_exists($defaultsetting, $this->choices)) {
3067                 return $defaultsetting;
3068             } else {
3069                 return '';
3070             }
3071     }
3073     /**
3074      * Returns the current setting
3075      *
3076      * @return mixed array or string
3077      */
3078     public function get_setting() {
3079         $result = parent::get_setting();
3080         if ($result === null) {
3081             // this is necessary for settings upgrade
3082             return null;
3083         }
3084         if (empty($result)) {
3085             $result = array('$@NONE@$');
3086         }
3087         return $result;
3088     }
3090     /**
3091      * Save the chosen setting provided as $data
3092      *
3093      * @param array $data
3094      * @return mixed string or array
3095      */
3096     public function write_setting($data) {
3097     // If all is selected, remove any explicit options.
3098         if (in_array('$@ALL@$', $data)) {
3099             $data = array('$@ALL@$');
3100         }
3101         // None never needs to be written to the DB.
3102         if (in_array('$@NONE@$', $data)) {
3103             unset($data[array_search('$@NONE@$', $data)]);
3104         }
3105         return parent::write_setting($data);
3106     }
3110 /**
3111  * Special checkbox for calendar - resets SESSION vars.
3112  *
3113  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3114  */
3115 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
3116     /**
3117      * Calls the parent::__construct with default values
3118      *
3119      * name =>  calendar_adminseesall
3120      * visiblename => get_string('adminseesall', 'admin')
3121      * description => get_string('helpadminseesall', 'admin')
3122      * defaultsetting => 0
3123      */
3124     public function __construct() {
3125         parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
3126             get_string('helpadminseesall', 'admin'), '0');
3127     }
3129     /**
3130      * Stores the setting passed in $data
3131      *
3132      * @param mixed gets converted to string for comparison
3133      * @return string empty string or error message
3134      */
3135     public function write_setting($data) {
3136         global $SESSION;
3137         return parent::write_setting($data);
3138     }
3141 /**
3142  * Special select for settings that are altered in setup.php and can not be altered on the fly
3143  *
3144  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3145  */
3146 class admin_setting_special_selectsetup extends admin_setting_configselect {
3147     /**
3148      * Reads the setting directly from the database
3149      *
3150      * @return mixed
3151      */
3152     public function get_setting() {
3153     // read directly from db!
3154         return get_config(NULL, $this->name);
3155     }
3157     /**
3158      * Save the setting passed in $data
3159      *
3160      * @param string $data The setting to save
3161      * @return string empty or error message
3162      */
3163     public function write_setting($data) {
3164         global $CFG;
3165         // do not change active CFG setting!
3166         $current = $CFG->{$this->name};
3167         $result = parent::write_setting($data);
3168         $CFG->{$this->name} = $current;
3169         return $result;
3170     }
3174 /**
3175  * Special select for frontpage - stores data in course table
3176  *
3177  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3178  */
3179 class admin_setting_sitesetselect extends admin_setting_configselect {
3180     /**
3181      * Returns the site name for the selected site
3182      *
3183      * @see get_site()
3184      * @return string The site name of the selected site
3185      */
3186     public function get_setting() {
3187         $site = course_get_format(get_site())->get_course();
3188         return $site->{$this->name};
3189     }
3191     /**
3192      * Updates the database and save the setting
3193      *
3194      * @param string data
3195      * @return string empty or error message
3196      */
3197     public function write_setting($data) {
3198         global $DB, $SITE;
3199         if (!in_array($data, array_keys($this->choices))) {
3200             return get_string('errorsetting', 'admin');
3201         }
3202         $record = new stdClass();
3203         $record->id           = SITEID;
3204         $temp                 = $this->name;
3205         $record->$temp        = $data;
3206         $record->timemodified = time();
3207         // update $SITE
3208         $SITE->{$this->name} = $data;
3209         course_get_format($SITE)->update_course_format_options($record);
3210         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3211     }
3215 /**
3216  * Select for blog's bloglevel setting: if set to 0, will set blog_menu
3217  * block to hidden.
3218  *
3219  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3220  */
3221 class admin_setting_bloglevel extends admin_setting_configselect {
3222     /**
3223      * Updates the database and save the setting
3224      *
3225      * @param string data
3226      * @return string empty or error message
3227      */
3228     public function write_setting($data) {
3229         global $DB, $CFG;
3230         if ($data == 0) {
3231             $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
3232             foreach ($blogblocks as $block) {
3233                 $DB->set_field('block', 'visible', 0, array('id' => $block->id));
3234             }
3235         } else {
3236             // reenable all blocks only when switching from disabled blogs
3237             if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
3238                 $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
3239                 foreach ($blogblocks as $block) {
3240                     $DB->set_field('block', 'visible', 1, array('id' => $block->id));
3241                 }
3242             }
3243         }
3244         return parent::write_setting($data);
3245     }
3249 /**
3250  * Special select - lists on the frontpage - hacky
3251  *
3252  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3253  */
3254 class admin_setting_courselist_frontpage extends admin_setting {
3255     /** @var array Array of choices value=>label */
3256     public $choices;
3258     /**
3259      * Construct override, requires one param
3260      *
3261      * @param bool $loggedin Is the user logged in
3262      */
3263     public function __construct($loggedin) {
3264         global $CFG;
3265         require_once($CFG->dirroot.'/course/lib.php');
3266         $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
3267         $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
3268         $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
3269         $defaults    = array(FRONTPAGECOURSELIST);
3270         parent::__construct($name, $visiblename, $description, $defaults);
3271     }
3273     /**
3274      * Loads the choices available
3275      *
3276      * @return bool always returns true
3277      */
3278     public function load_choices() {
3279         global $DB;
3280         if (is_array($this->choices)) {
3281             return true;
3282         }
3283         $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
3284             FRONTPAGECOURSELIST    => get_string('frontpagecourselist'),
3285             FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
3286             FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
3287             'none'                 => get_string('none'));
3288         if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
3289             unset($this->choices[FRONTPAGECOURSELIST]);
3290         }
3291         return true;
3292     }
3294     /**
3295      * Returns the selected settings
3296      *
3297      * @param mixed array or setting or null
3298      */
3299     public function get_setting() {
3300         $result = $this->config_read($this->name);
3301         if (is_null($result)) {
3302             return NULL;
3303         }
3304         if ($result === '') {
3305             return array();
3306         }
3307         return explode(',', $result);
3308     }
3310     /**
3311      * Save the selected options
3312      *
3313      * @param array $data
3314      * @return mixed empty string (data is not an array) or bool true=success false=failure
3315      */
3316     public function write_setting($data) {
3317         if (!is_array($data)) {
3318             return '';
3319         }
3320         $this->load_choices();
3321         $save = array();
3322         foreach($data as $datum) {
3323             if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
3324                 continue;
3325             }
3326             $save[$datum] = $datum; // no duplicates
3327         }
3328         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3329     }
3331     /**
3332      * Return XHTML select field and wrapping div
3333      *
3334      * @todo Add vartype handling to make sure $data is an array
3335      * @param array $data Array of elements to select by default
3336      * @return string XHTML select field and wrapping div
3337      */
3338     public function output_html($data, $query='') {
3339         $this->load_choices();
3340         $currentsetting = array();
3341         foreach ($data as $key) {
3342             if ($key != 'none' and array_key_exists($key, $this->choices)) {
3343                 $currentsetting[] = $key; // already selected first
3344             }
3345         }
3347         $return = '<div class="form-group">';
3348         for ($i = 0; $i < count($this->choices) - 1; $i++) {
3349             if (!array_key_exists($i, $currentsetting)) {
3350                 $currentsetting[$i] = 'none'; //none
3351             }
3352             $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
3353             foreach ($this->choices as $key => $value) {
3354                 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
3355             }
3356             $return .= '</select>';
3357             if ($i !== count($this->choices) - 2) {
3358                 $return .= '<br />';
3359             }
3360         }
3361         $return .= '</div>';
3363         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3364     }
3368 /**
3369  * Special checkbox for frontpage - stores data in course table
3370  *
3371  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3372  */
3373 class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
3374     /**
3375      * Returns the current sites name
3376      *
3377      * @return string
3378      */
3379     public function get_setting() {
3380         $site = course_get_format(get_site())->get_course();
3381         return $site->{$this->name};
3382     }
3384     /**
3385      * Save the selected setting
3386      *
3387      * @param string $data The selected site
3388      * @return string empty string or error message
3389      */
3390     public function write_setting($data) {
3391         global $DB, $SITE;
3392         $record = new stdClass();
3393         $record->id            = SITEID;
3394         $record->{$this->name} = ($data == '1' ? 1 : 0);
3395         $record->timemodified  = time();
3396         // update $SITE
3397         $SITE->{$this->name} = $data;
3398         course_get_format($SITE)->update_course_format_options($record);
3399         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3400     }
3403 /**
3404  * Special text for frontpage - stores data in course table.
3405  * Empty string means not set here. Manual setting is required.
3406  *
3407  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3408  */
3409 class admin_setting_sitesettext extends admin_setting_configtext {
3410     /**
3411      * Return the current setting
3412      *
3413      * @return mixed string or null
3414      */
3415     public function get_setting() {
3416         $site = course_get_format(get_site())->get_course();
3417         return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
3418     }
3420     /**
3421      * Validate the selected data
3422      *
3423      * @param string $data The selected value to validate
3424      * @return mixed true or message string
3425      */
3426     public function validate($data) {
3427         $cleaned = clean_param($data, PARAM_TEXT);
3428         if ($cleaned === '') {
3429             return get_string('required');
3430         }
3431         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
3432             return true;
3433         } else {
3434             return get_string('validateerror', 'admin');
3435         }
3436     }
3438     /**
3439      * Save the selected setting
3440      *
3441      * @param string $data The selected value
3442      * @return string empty or error message
3443      */
3444     public function write_setting($data) {
3445         global $DB, $SITE;
3446         $data = trim($data);
3447         $validated = $this->validate($data);
3448         if ($validated !== true) {
3449             return $validated;
3450         }
3452         $record = new stdClass();
3453         $record->id            = SITEID;
3454         $record->{$this->name} = $data;
3455         $record->timemodified  = time();
3456         // update $SITE
3457         $SITE->{$this->name} = $data;
3458         course_get_format($SITE)->update_course_format_options($record);
3459         return ($DB->update_record('course', $record) ? '' : get_string('dbupdatefailed', 'error'));
3460     }
3464 /**
3465  * Special text editor for site description.
3466  *
3467  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3468  */
3469 class admin_setting_special_frontpagedesc extends admin_setting {
3470     /**
3471      * Calls parent::__construct with specific arguments
3472      */
3473     public function __construct() {
3474         parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
3475         editors_head_setup();
3476     }
3478     /**
3479      * Return the current setting
3480      * @return string The current setting
3481      */
3482     public function get_setting() {
3483         $site = course_get_format(get_site())->get_course();
3484         return $site->{$this->name};
3485     }
3487     /**
3488      * Save the new setting
3489      *
3490      * @param string $data The new value to save
3491      * @return string empty or error message
3492      */
3493     public function write_setting($data) {
3494         global $DB, $SITE;
3495         $record = new stdClass();
3496         $record->id            = SITEID;
3497         $record->{$this->name} = $data;
3498         $record->timemodified  = time();
3499         $SITE->{$this->name} = $data;
3500         course_get_format($SITE)->update_course_format_options($record);
3501         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3502     }
3504     /**
3505      * Returns XHTML for the field plus wrapping div
3506      *
3507      * @param string $data The current value
3508      * @param string $query
3509      * @return string The XHTML output
3510      */
3511     public function output_html($data, $query='') {
3512         global $CFG;
3514         $CFG->adminusehtmleditor = can_use_html_editor();
3515         $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
3517         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3518     }
3522 /**
3523  * Administration interface for emoticon_manager settings.
3524  *
3525  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3526  */
3527 class admin_setting_emoticons extends admin_setting {
3529     /**
3530      * Calls parent::__construct with specific args
3531      */
3532     public function __construct() {
3533         global $CFG;
3535         $manager = get_emoticon_manager();
3536         $defaults = $this->prepare_form_data($manager->default_emoticons());
3537         parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
3538     }
3540     /**
3541      * Return the current setting(s)
3542      *
3543      * @return array Current settings array
3544      */
3545     public function get_setting() {
3546         global $CFG;
3548         $manager = get_emoticon_manager();
3550         $config = $this->config_read($this->name);
3551         if (is_null($config)) {
3552             return null;
3553         }
3555         $config = $manager->decode_stored_config($config);
3556         if (is_null($config)) {
3557             return null;
3558         }
3560         return $this->prepare_form_data($config);
3561     }
3563     /**
3564      * Save selected settings
3565      *
3566      * @param array $data Array of settings to save
3567      * @return bool
3568      */
3569     public function write_setting($data) {
3571         $manager = get_emoticon_manager();
3572         $emoticons = $this->process_form_data($data);
3574         if ($emoticons === false) {
3575             return false;
3576         }
3578         if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
3579             return ''; // success
3580         } else {
3581             return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
3582         }
3583     }
3585     /**
3586      * Return XHTML field(s) for options
3587      *
3588      * @param array $data Array of options to set in HTML
3589      * @return string XHTML string for the fields and wrapping div(s)
3590      */
3591     public function output_html($data, $query='') {
3592         global $OUTPUT;
3594         $out  = html_writer::start_tag('table', array('border' => 1, 'class' => 'generaltable'));
3595         $out .= html_writer::start_tag('thead');
3596         $out .= html_writer::start_tag('tr');
3597         $out .= html_writer::tag('th', get_string('emoticontext', 'admin'));
3598         $out .= html_writer::tag('th', get_string('emoticonimagename', 'admin'));
3599         $out .= html_writer::tag('th', get_string('emoticoncomponent', 'admin'));
3600         $out .= html_writer::tag('th', get_string('emoticonalt', 'admin'), array('colspan' => 2));
3601         $out .= html_writer::tag('th', '');
3602         $out .= html_writer::end_tag('tr');
3603         $out .= html_writer::end_tag('thead');
3604         $out .= html_writer::start_tag('tbody');
3605         $i = 0;
3606         foreach($data as $field => $value) {
3607             switch ($i) {
3608             case 0:
3609                 $out .= html_writer::start_tag('tr');
3610                 $current_text = $value;
3611                 $current_filename = '';
3612                 $current_imagecomponent = '';
3613                 $current_altidentifier = '';
3614                 $current_altcomponent = '';
3615             case 1:
3616                 $current_filename = $value;
3617             case 2:
3618                 $current_imagecomponent = $value;
3619             case 3:
3620                 $current_altidentifier = $value;
3621             case 4:
3622                 $current_altcomponent = $value;
3623             }
3625             $out .= html_writer::tag('td',
3626                 html_writer::empty_tag('input',
3627                     array(
3628                         'type'  => 'text',
3629                         'class' => 'form-text',
3630                         'name'  => $this->get_full_name().'['.$field.']',
3631                         'value' => $value,
3632                     )
3633                 ), array('class' => 'c'.$i)
3634             );
3636             if ($i == 4) {
3637                 if (get_string_manager()->string_exists($current_altidentifier, $current_altcomponent)) {
3638                     $alt = get_string($current_altidentifier, $current_altcomponent);
3639                 } else {
3640                     $alt = $current_text;
3641                 }
3642                 if ($current_filename) {
3643                     $out .= html_writer::tag('td', $OUTPUT->render(new pix_emoticon($current_filename, $alt, $current_imagecomponent)));
3644                 } else {
3645                     $out .= html_writer::tag('td', '');
3646                 }
3647                 $out .= html_writer::end_tag('tr');
3648                 $i = 0;
3649             } else {
3650                 $i++;
3651             }
3653         }
3654         $out .= html_writer::end_tag('tbody');
3655         $out .= html_writer::end_tag('table');
3656         $out  = html_writer::tag('div', $out, array('class' => 'form-group'));
3657         $out .= html_writer::tag('div', html_writer::link(new moodle_url('/admin/resetemoticons.php'), get_string('emoticonsreset', 'admin')));
3659         return format_admin_setting($this, $this->visiblename, $out, $this->description, false, '', NULL, $query);
3660     }
3662     /**
3663      * Converts the array of emoticon objects provided by {@see emoticon_manager} into admin settings form data
3664      *
3665      * @see self::process_form_data()
3666      * @param array $emoticons array of emoticon objects as returned by {@see emoticon_manager}
3667      * @return array of form fields and their values
3668      */
3669     protected function prepare_form_data(array $emoticons) {
3671         $form = array();
3672         $i = 0;
3673         foreach ($emoticons as $emoticon) {
3674             $form['text'.$i]            = $emoticon->text;
3675             $form['imagename'.$i]       = $emoticon->imagename;
3676             $form['imagecomponent'.$i]  = $emoticon->imagecomponent;
3677             $form['altidentifier'.$i]   = $emoticon->altidentifier;
3678             $form['altcomponent'.$i]    = $emoticon->altcomponent;
3679             $i++;
3680         }
3681         // add one more blank field set for new object
3682         $form['text'.$i]            = '';
3683         $form['imagename'.$i]       = '';
3684         $form['imagecomponent'.$i]  = '';
3685         $form['altidentifier'.$i]   = '';
3686         $form['altcomponent'.$i]    = '';
3688         return $form;
3689     }
3691     /**
3692      * Converts the data from admin settings form into an array of emoticon objects
3693      *
3694      * @see self::prepare_form_data()
3695      * @param array $data array of admin form fields and values
3696      * @return false|array of emoticon objects
3697      */
3698     protected function process_form_data(array $form) {
3700         $count = count($form); // number of form field values
3702         if ($count % 5) {
3703             // we must get five fields per emoticon object
3704             return false;
3705         }
3707         $emoticons = array();
3708         for ($i = 0; $i < $count / 5; $i++) {
3709             $emoticon                   = new stdClass();
3710             $emoticon->text             = clean_param(trim($form['text'.$i]), PARAM_NOTAGS);
3711             $emoticon->imagename        = clean_param(trim($form['imagename'.$i]), PARAM_PATH);
3712             $emoticon->imagecomponent   = clean_param(trim($form['imagecomponent'.$i]), PARAM_COMPONENT);
3713             $emoticon->altidentifier    = clean_param(trim($form['altidentifier'.$i]), PARAM_STRINGID);
3714             $emoticon->altcomponent     = clean_param(trim($form['altcomponent'.$i]), PARAM_COMPONENT);
3716             if (strpos($emoticon->text, ':/') !== false or strpos($emoticon->text, '//') !== false) {
3717                 // prevent from breaking http://url.addresses by accident
3718                 $emoticon->text = '';
3719             }
3721             if (strlen($emoticon->text) < 2) {
3722                 // do not allow single character emoticons
3723                 $emoticon->text = '';
3724             }
3726             if (preg_match('/^[a-zA-Z]+[a-zA-Z0-9]*$/', $emoticon->text)) {
3727                 // emoticon text must contain some non-alphanumeric character to prevent
3728                 // breaking HTML tags
3729                 $emoticon->text = '';
3730             }
3732             if ($emoticon->text !== '' and $emoticon->imagename !== '' and $emoticon->imagecomponent !== '') {
3733                 $emoticons[] = $emoticon;
3734             }
3735         }
3736         return $emoticons;
3737     }
3741 /**
3742  * Special setting for limiting of the list of available languages.
3743  *
3744  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3745  */
3746 class admin_setting_langlist extends admin_setting_configtext {
3747     /**
3748      * Calls parent::__construct with specific arguments
3749      */
3750     public function __construct() {
3751         parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
3752     }
3754     /**
3755      * Save the new setting
3756      *
3757      * @param string $data The new setting
3758      * @return bool
3759      */
3760     public function write_setting($data) {
3761         $return = parent::write_setting($data);
3762         get_string_manager()->reset_caches();
3763         return $return;
3764     }
3768 /**
3769  * Selection of one of the recognised countries using the list
3770  * returned by {@link get_list_of_countries()}.
3771  *
3772  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3773  */
3774 class admin_settings_country_select extends admin_setting_configselect {
3775     protected $includeall;
3776     public function __construct($name, $visiblename, $description, $defaultsetting, $includeall=false) {
3777         $this->includeall = $includeall;
3778         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
3779     }
3781     /**
3782      * Lazy-load the available choices for the select box
3783      */
3784     public function load_choices() {
3785         global $CFG;
3786         if (is_array($this->choices)) {
3787             return true;
3788         }
3789         $this->choices = array_merge(
3790                 array('0' => get_string('choosedots')),
3791                 get_string_manager()->get_list_of_countries($this->includeall));
3792         return true;
3793     }
3797 /**
3798  * admin_setting_configselect for the default number of sections in a course,
3799  * simply so we can lazy-load the choices.
3800  *
3801  * @copyright 2011 The Open University
3802  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3803  */
3804 class admin_settings_num_course_sections extends admin_setting_configselect {
3805     public function __construct($name, $visiblename, $description, $defaultsetting) {
3806         parent::__construct($name, $visiblename, $description, $defaultsetting, array());
3807     }
3809     /** Lazy-load the available choices for the select box */
3810     public function load_choices() {
3811         $max = get_config('moodlecourse', 'maxsections');
3812         if (empty($max)) {
3813             $max = 52;
3814         }
3815         for ($i = 0; $i <= $max; $i++) {
3816             $this->choices[$i] = "$i";
3817         }
3818         return true;
3819     }
3823 /**
3824  * Course category selection
3825  *
3826  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3827  */
3828 class admin_settings_coursecat_select extends admin_setting_configselect {
3829     /**
3830      * Calls parent::__construct with specific arguments
3831      */
3832     public function __construct($name, $visiblename, $description, $defaultsetting) {
3833         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
3834     }
3836     /**
3837      * Load the available choices for the select box
3838      *
3839      * @return bool
3840      */
3841     public function load_choices() {
3842         global $CFG;
3843         require_once($CFG->dirroot.'/course/lib.php');
3844         if (is_array($this->choices)) {
3845             return true;
3846         }
3847         $this->choices = make_categories_options();
3848         return true;
3849     }
3853 /**
3854  * Special control for selecting days to backup
3855  *
3856  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3857  */
3858 class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
3859     /**
3860      * Calls parent::__construct with specific arguments
3861      */
3862     public function __construct() {
3863         parent::__construct('backup_auto_weekdays', get_string('automatedbackupschedule','backup'), get_string('automatedbackupschedulehelp','backup'), array(), NULL);
3864         $this->plugin = 'backup';
3865     }
3867     /**
3868      * Load the available choices for the select box
3869      *
3870      * @return bool Always returns true
3871      */
3872     public function load_choices() {
3873         if (is_array($this->choices)) {
3874             return true;
3875         }
3876         $this->choices = array();
3877         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
3878         foreach ($days as $day) {
3879             $this->choices[$day] = get_string($day, 'calendar');
3880         }
3881         return true;
3882     }
3886 /**
3887  * Special debug setting
3888  *
3889  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3890  */
3891 class admin_setting_special_debug extends admin_setting_configselect {
3892     /**
3893      * Calls parent::__construct with specific arguments
3894      */
3895     public function __construct() {
3896         parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
3897     }
3899     /**
3900      * Load the available choices for the select box
3901      *
3902      * @return bool
3903      */
3904     public function load_choices() {
3905         if (is_array($this->choices)) {
3906             return true;
3907         }
3908         $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
3909             DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
3910             DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
3911             DEBUG_ALL       => get_string('debugall', 'admin'),
3912             DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
3913         return true;
3914     }
3918 /**
3919  * Special admin control
3920  *
3921  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3922  */
3923 class admin_setting_special_calendar_weekend extends admin_setting {
3924     /**
3925      * Calls parent::__construct with specific arguments
3926      */
3927     public function __construct() {
3928         $name = 'calendar_weekend';
3929         $visiblename = get_string('calendar_weekend', 'admin');
3930         $description = get_string('helpweekenddays', 'admin');
3931         $default = array ('0', '6'); // Saturdays and Sundays
3932         parent::__construct($name, $visiblename, $description, $default);
3933     }
3935     /**
3936      * Gets the current settings as an array
3937      *
3938      * @return mixed Null if none, else array of settings
3939      */
3940     public function get_setting() {
3941         $result = $this->config_read($this->name);
3942         if (is_null($result)) {
3943             return NULL;
3944         }
3945         if ($result === '') {
3946             return array();
3947         }
3948         $settings = array();
3949         for ($i=0; $i<7; $i++) {
3950             if ($result & (1 << $i)) {
3951                 $settings[] = $i;
3952             }
3953         }
3954         return $settings;
3955     }
3957     /**
3958      * Save the new settings
3959      *
3960      * @param array $data Array of new settings
3961      * @return bool
3962      */
3963     public function write_setting($data) {
3964         if (!is_array($data)) {
3965             return '';
3966         }
3967         unset($data['xxxxx']);
3968         $result = 0;
3969         foreach($data as $index) {
3970             $result |= 1 << $index;
3971         }
3972         return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
3973     }
3975     /**
3976      * Return XHTML to display the control
3977      *
3978      * @param array $data array of selected days
3979      * @param string $query
3980      * @return string XHTML for display (field + wrapping div(s)
3981      */
3982     public function output_html($data, $query='') {
3983     // The order matters very much because of the implied numeric keys
3984         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
3985         $return = '<table><thead><tr>';
3986         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3987         foreach($days as $index => $day) {
3988             $return .= '<td><label for="'.$this->get_id().$index.'">'.get_string($day, 'calendar').'</label></td>';
3989         }
3990         $return .= '</tr></thead><tbody><tr>';
3991         foreach($days as $index => $day) {
3992             $return .= '<td><input type="checkbox" class="form-checkbox" id="'.$this->get_id().$index.'" name="'.$this->get_full_name().'[]" value="'.$index.'" '.(in_array("$index", $data) ? 'checked="checked"' : '').' /></td>';
3993         }
3994         $return .= '</tr></tbody></table>';
3996         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3998     }
4002 /**
4003  * Admin setting that allows a user to pick a behaviour.
4004  *
4005  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4006  */
4007 class admin_setting_question_behaviour extends admin_setting_configselect {
4008     /**
4009      * @param string $name name of config variable
4010      * @param string $visiblename display name
4011      * @param string $description description
4012      * @param string $default default.
4013      */
4014     public function __construct($name, $visiblename, $description, $default) {
4015         parent::__construct($name, $visiblename, $description, $default, NULL);
4016     }
4018     /**
4019      * Load list of behaviours as choices
4020      * @return bool true => success, false => error.
4021      */
4022     public function load_choices() {
4023         global $CFG;
4024         require_once($CFG->dirroot . '/question/engine/lib.php');
4025         $this->choices = question_engine::get_archetypal_behaviours();
4026         return true;
4027     }
4031 /**
4032  * Admin setting that allows a user to pick appropriate roles for something.
4033  *
4034  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4035  */
4036 class admin_setting_pickroles extends admin_setting_configmulticheckbox {
4037     /** @var array Array of capabilities which identify roles */
4038     private $types;
4040     /**
4041      * @param string $name Name of config variable
4042      * @param string $visiblename Display name
4043      * @param string $description Description
4044      * @param array $types Array of archetypes which identify
4045      *              roles that will be enabled by default.
4046      */
4047     public function __construct($name, $visiblename, $description, $types) {
4048         parent::__construct($name, $visiblename, $description, NULL, NULL);
4049         $this->types = $types;
4050     }
4052     /**
4053      * Load roles as choices
4054      *
4055      * @return bool true=>success, false=>error
4056      */
4057     public function load_choices() {
4058         global $CFG, $DB;
4059         if (during_initial_install()) {
4060             return false;
4061         }
4062         if (is_array($this->choices)) {
4063             return true;
4064         }
4065         if ($roles = get_all_roles()) {
4066             $this->choices = role_fix_names($roles, null, ROLENAME_ORIGINAL, true);
4067             return true;
4068         } else {
4069             return false;
4070         }
4071     }
4073     /**
4074      * Return the default setting for this control
4075      *
4076      * @return array Array of default settings
4077      */
4078     public function get_defaultsetting() {
4079         global $CFG;
4081         if (during_initial_install()) {
4082             return null;
4083         }
4084         $result = array();
4085         foreach($this->types as $archetype) {
4086             if ($caproles = get_archetype_roles($archetype)) {
4087                 foreach ($caproles as $caprole) {
4088                     $result[$caprole->id] = 1;
4089                 }
4090             }
4091         }
4092         return $result;
4093     }
4097 /**
4098  * Text field with an advanced checkbox, that controls a additional $name.'_adv' setting.
4099  *
4100  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4101  */
4102 class admin_setting_configtext_with_advanced extends admin_setting_configtext {
4103     /**
4104      * Constructor
4105      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
4106      * @param string $visiblename localised
4107      * @param string $description long localised info
4108      * @param array $defaultsetting ('value'=>string, '__construct'=>bool)
4109      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
4110      * @param int $size default field size
4111      */
4112     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
4113         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
4114     }
4116     /**
4117      * Loads the current setting and returns array
4118      *
4119      * @return array Returns array value=>xx, __construct=>xx
4120      */
4121     public function get_setting() {
4122         $value = parent::get_setting();
4123         $adv = $this->config_read($this->name.'_adv');
4124         if (is_null($value) or is_null($adv)) {
4125             return NULL;
4126         }
4127         return array('value' => $value, 'adv' => $adv);
4128     }
4130     /**
4131      * Saves the new settings passed in $data
4132      *
4133      * @todo Add vartype handling to ensure $data is an array
4134      * @param array $data
4135      * @return mixed string or Array
4136      */
4137     public function write_setting($data) {
4138         $error = parent::write_setting($data['value']);
4139         if (!$error) {
4140             $value = empty($data['adv']) ? 0 : 1;
4141             $this->config_write($this->name.'_adv', $value);
4142         }
4143         return $error;
4144     }
4146     /**
4147      * Return XHTML for the control
4148      *
4149      * @param array $data Default data array
4150      * @param string $query
4151      * @return string XHTML to display control
4152      */
4153     public function output_html($data, $query='') {
4154         $default = $this->get_defaultsetting();
4155         $defaultinfo = array();
4156         if (isset($default['value'])) {
4157             if ($default['value'] === '') {
4158                 $defaultinfo[] = "''";
4159             } else {
4160                 $defaultinfo[] = $default['value'];
4161             }
4162         }
4163         if (!empty($default['adv'])) {
4164             $defaultinfo[] = get_string('advanced');
4165         }
4166         $defaultinfo = implode(', ', $defaultinfo);
4168         $adv = !empty($data['adv']);
4169         $return = '<div class="form-text defaultsnext">' .
4170             '<input type="text" size="' . $this->size . '" id="' . $this->get_id() .
4171             '" name="' . $this->get_full_name() . '[value]" value="' . s($data['value']) . '" />' .
4172             ' <input type="checkbox" class="form-checkbox" id="' .
4173             $this->get_id() . '_adv" name="' . $this->get_full_name() .
4174             '[adv]" value="1" ' . ($adv ? 'checked="checked"' : '') . ' />' .
4175             ' <label for="' . $this->get_id() . '_adv">' .
4176             get_string('advanced') . '</label></div>';
4178         return format_admin_setting($this, $this->visiblename, $return,
4179         $this->description, true, '', $defaultinfo, $query);
4180     }
4184 /**
4185  * Checkbox with an advanced checkbox that controls an additional $name.'_adv' config setting.
4186  *
4187  * @copyright 2009 Petr Skoda (http://skodak.org)
4188  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4189  */
4190 class admin_setting_configcheckbox_with_advanced extends admin_setting_configcheckbox {
4192     /**
4193      * Constructor
4194      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
4195      * @param string $visiblename localised
4196      * @param string $description long localised info
4197      * @param array $defaultsetting ('value'=>string, 'adv'=>bool)
4198      * @param string $yes value used when checked
4199      * @param string $no value used when not checked
4200      */
4201     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
4202         parent::__construct($name, $visiblename, $description, $defaultsetting, $yes, $no);
4203     }
4205     /**
4206      * Loads the current setting and returns array
4207      *
4208      * @return array Returns array value=>xx, adv=>xx
4209      */
4210     public function get_setting() {
4211         $value = parent::get_setting();
4212         $adv = $this->config_read($this->name.'_adv');
4213         if (is_null($value) or is_null($adv)) {
4214             return NULL;
4215         }
4216         return array('value' => $value, 'adv' => $adv);
4217     }
4219     /**
4220      * Sets the value for the setting
4221      *
4222      * Sets the value for the setting to either the yes or no values
4223      * of the object by comparing $data to yes
4224      *
4225      * @param mixed $data Gets converted to str for comparison against yes value
4226      * @return string empty string or error
4227      */
4228     public function write_setting($data) {
4229         $error = parent::write_setting($data['value']);
4230         if (!$error) {
4231             $value = empty($data['adv']) ? 0 : 1;
4232             $this->config_write($this->name.'_adv', $value);
4233         }
4234         return $error;
4235     }
4237     /**
4238      * Returns an XHTML checkbox field and with extra advanced cehckbox
4239      *
4240      * @param string $data If $data matches yes then checkbox is checked
4241      * @param string $query