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