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