Merge branch 'w51_MDL-37191_m25_plugindelete' of git://github.com/skodak/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             $parent->children[] = $something;
936             if (is_array($this->category_cache) and ($something instanceof admin_category)) {
937                 if (isset($this->category_cache[$something->name])) {
938                     debugging('Duplicate admin category name: '.$something->name);
939                 } else {
940                     $this->category_cache[$something->name] = $something;
941                     $something->category_cache =& $this->category_cache;
942                     foreach ($something->children as $child) {
943                         // just in case somebody already added subcategories
944                         if ($child instanceof admin_category) {
945                             if (isset($this->category_cache[$child->name])) {
946                                 debugging('Duplicate admin category name: '.$child->name);
947                             } else {
948                                 $this->category_cache[$child->name] = $child;
949                                 $child->category_cache =& $this->category_cache;
950                             }
951                         }
952                     }
953                 }
954             }
955             return true;
957         } else {
958             debugging('error - can not add this element');
959             return false;
960         }
962     }
964     /**
965      * Checks if the user has access to anything in this category.
966      *
967      * @return bool True if the user has access to at least one child in this category, false otherwise.
968      */
969     public function check_access() {
970         foreach ($this->children as $child) {
971             if ($child->check_access()) {
972                 return true;
973             }
974         }
975         return false;
976     }
978     /**
979      * Is this category hidden in admin tree block?
980      *
981      * @return bool True if hidden
982      */
983     public function is_hidden() {
984         return $this->hidden;
985     }
987     /**
988      * Show we display Save button at the page bottom?
989      * @return bool
990      */
991     public function show_save() {
992         foreach ($this->children as $child) {
993             if ($child->show_save()) {
994                 return true;
995             }
996         }
997         return false;
998     }
1002 /**
1003  * Root of admin settings tree, does not have any parent.
1004  *
1005  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1006  */
1007 class admin_root extends admin_category {
1008 /** @var array List of errors */
1009     public $errors;
1010     /** @var string search query */
1011     public $search;
1012     /** @var bool full tree flag - true means all settings required, false only pages required */
1013     public $fulltree;
1014     /** @var bool flag indicating loaded tree */
1015     public $loaded;
1016     /** @var mixed site custom defaults overriding defaults in settings files*/
1017     public $custom_defaults;
1019     /**
1020      * @param bool $fulltree true means all settings required,
1021      *                            false only pages required
1022      */
1023     public function __construct($fulltree) {
1024         global $CFG;
1026         parent::__construct('root', get_string('administration'), false);
1027         $this->errors   = array();
1028         $this->search   = '';
1029         $this->fulltree = $fulltree;
1030         $this->loaded   = false;
1032         $this->category_cache = array();
1034         // load custom defaults if found
1035         $this->custom_defaults = null;
1036         $defaultsfile = "$CFG->dirroot/local/defaults.php";
1037         if (is_readable($defaultsfile)) {
1038             $defaults = array();
1039             include($defaultsfile);
1040             if (is_array($defaults) and count($defaults)) {
1041                 $this->custom_defaults = $defaults;
1042             }
1043         }
1044     }
1046     /**
1047      * Empties children array, and sets loaded to false
1048      *
1049      * @param bool $requirefulltree
1050      */
1051     public function purge_children($requirefulltree) {
1052         $this->children = array();
1053         $this->fulltree = ($requirefulltree || $this->fulltree);
1054         $this->loaded   = false;
1055         //break circular dependencies - this helps PHP 5.2
1056         while($this->category_cache) {
1057             array_pop($this->category_cache);
1058         }
1059         $this->category_cache = array();
1060     }
1064 /**
1065  * Links external PHP pages into the admin tree.
1066  *
1067  * See detailed usage example at the top of this document (adminlib.php)
1068  *
1069  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1070  */
1071 class admin_externalpage implements part_of_admin_tree {
1073     /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1074     public $name;
1076     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1077     public $visiblename;
1079     /** @var string The external URL that we should link to when someone requests this external page. */
1080     public $url;
1082     /** @var string The role capability/permission a user must have to access this external page. */
1083     public $req_capability;
1085     /** @var object The context in which capability/permission should be checked, default is site context. */
1086     public $context;
1088     /** @var bool hidden in admin tree block. */
1089     public $hidden;
1091     /** @var mixed either string or array of string */
1092     public $path;
1094     /** @var array list of visible names of page parents */
1095     public $visiblepath;
1097     /**
1098      * Constructor for adding an external page into the admin tree.
1099      *
1100      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1101      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1102      * @param string $url The external URL that we should link to when someone requests this external page.
1103      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1104      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1105      * @param stdClass $context The context the page relates to. Not sure what happens
1106      *      if you specify something other than system or front page. Defaults to system.
1107      */
1108     public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1109         $this->name        = $name;
1110         $this->visiblename = $visiblename;
1111         $this->url         = $url;
1112         if (is_array($req_capability)) {
1113             $this->req_capability = $req_capability;
1114         } else {
1115             $this->req_capability = array($req_capability);
1116         }
1117         $this->hidden = $hidden;
1118         $this->context = $context;
1119     }
1121     /**
1122      * Returns a reference to the part_of_admin_tree object with internal name $name.
1123      *
1124      * @param string $name The internal name of the object we want.
1125      * @param bool $findpath defaults to false
1126      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1127      */
1128     public function locate($name, $findpath=false) {
1129         if ($this->name == $name) {
1130             if ($findpath) {
1131                 $this->visiblepath = array($this->visiblename);
1132                 $this->path        = array($this->name);
1133             }
1134             return $this;
1135         } else {
1136             $return = NULL;
1137             return $return;
1138         }
1139     }
1141     /**
1142      * This function always returns false, required function by interface
1143      *
1144      * @param string $name
1145      * @return false
1146      */
1147     public function prune($name) {
1148         return false;
1149     }
1151     /**
1152      * Search using query
1153      *
1154      * @param string $query
1155      * @return mixed array-object structure of found settings and pages
1156      */
1157     public function search($query) {
1158         $found = false;
1159         if (strpos(strtolower($this->name), $query) !== false) {
1160             $found = true;
1161         } else if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1162                 $found = true;
1163             }
1164         if ($found) {
1165             $result = new stdClass();
1166             $result->page     = $this;
1167             $result->settings = array();
1168             return array($this->name => $result);
1169         } else {
1170             return array();
1171         }
1172     }
1174     /**
1175      * Determines if the current user has access to this external page based on $this->req_capability.
1176      *
1177      * @return bool True if user has access, false otherwise.
1178      */
1179     public function check_access() {
1180         global $CFG;
1181         $context = empty($this->context) ? context_system::instance() : $this->context;
1182         foreach($this->req_capability as $cap) {
1183             if (has_capability($cap, $context)) {
1184                 return true;
1185             }
1186         }
1187         return false;
1188     }
1190     /**
1191      * Is this external page hidden in admin tree block?
1192      *
1193      * @return bool True if hidden
1194      */
1195     public function is_hidden() {
1196         return $this->hidden;
1197     }
1199     /**
1200      * Show we display Save button at the page bottom?
1201      * @return bool
1202      */
1203     public function show_save() {
1204         return false;
1205     }
1209 /**
1210  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1211  *
1212  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1213  */
1214 class admin_settingpage implements part_of_admin_tree {
1216     /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1217     public $name;
1219     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1220     public $visiblename;
1222     /** @var mixed An array of admin_setting objects that are part of this setting page. */
1223     public $settings;
1225     /** @var string The role capability/permission a user must have to access this external page. */
1226     public $req_capability;
1228     /** @var object The context in which capability/permission should be checked, default is site context. */
1229     public $context;
1231     /** @var bool hidden in admin tree block. */
1232     public $hidden;
1234     /** @var mixed string of paths or array of strings of paths */
1235     public $path;
1237     /** @var array list of visible names of page parents */
1238     public $visiblepath;
1240     /**
1241      * see admin_settingpage for details of this function
1242      *
1243      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1244      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1245      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1246      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1247      * @param stdClass $context The context the page relates to. Not sure what happens
1248      *      if you specify something other than system or front page. Defaults to system.
1249      */
1250     public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1251         $this->settings    = new stdClass();
1252         $this->name        = $name;
1253         $this->visiblename = $visiblename;
1254         if (is_array($req_capability)) {
1255             $this->req_capability = $req_capability;
1256         } else {
1257             $this->req_capability = array($req_capability);
1258         }
1259         $this->hidden      = $hidden;
1260         $this->context     = $context;
1261     }
1263     /**
1264      * see admin_category
1265      *
1266      * @param string $name
1267      * @param bool $findpath
1268      * @return mixed Object (this) if name ==  this->name, else returns null
1269      */
1270     public function locate($name, $findpath=false) {
1271         if ($this->name == $name) {
1272             if ($findpath) {
1273                 $this->visiblepath = array($this->visiblename);
1274                 $this->path        = array($this->name);
1275             }
1276             return $this;
1277         } else {
1278             $return = NULL;
1279             return $return;
1280         }
1281     }
1283     /**
1284      * Search string in settings page.
1285      *
1286      * @param string $query
1287      * @return array
1288      */
1289     public function search($query) {
1290         $found = array();
1292         foreach ($this->settings as $setting) {
1293             if ($setting->is_related($query)) {
1294                 $found[] = $setting;
1295             }
1296         }
1298         if ($found) {
1299             $result = new stdClass();
1300             $result->page     = $this;
1301             $result->settings = $found;
1302             return array($this->name => $result);
1303         }
1305         $found = false;
1306         if (strpos(strtolower($this->name), $query) !== false) {
1307             $found = true;
1308         } else if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1309                 $found = true;
1310             }
1311         if ($found) {
1312             $result = new stdClass();
1313             $result->page     = $this;
1314             $result->settings = array();
1315             return array($this->name => $result);
1316         } else {
1317             return array();
1318         }
1319     }
1321     /**
1322      * This function always returns false, required by interface
1323      *
1324      * @param string $name
1325      * @return bool Always false
1326      */
1327     public function prune($name) {
1328         return false;
1329     }
1331     /**
1332      * adds an admin_setting to this admin_settingpage
1333      *
1334      * 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
1335      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1336      *
1337      * @param object $setting is the admin_setting object you want to add
1338      * @return bool true if successful, false if not
1339      */
1340     public function add($setting) {
1341         if (!($setting instanceof admin_setting)) {
1342             debugging('error - not a setting instance');
1343             return false;
1344         }
1346         $this->settings->{$setting->name} = $setting;
1347         return true;
1348     }
1350     /**
1351      * see admin_externalpage
1352      *
1353      * @return bool Returns true for yes false for no
1354      */
1355     public function check_access() {
1356         global $CFG;
1357         $context = empty($this->context) ? context_system::instance() : $this->context;
1358         foreach($this->req_capability as $cap) {
1359             if (has_capability($cap, $context)) {
1360                 return true;
1361             }
1362         }
1363         return false;
1364     }
1366     /**
1367      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1368      * @return string Returns an XHTML string
1369      */
1370     public function output_html() {
1371         $adminroot = admin_get_root();
1372         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1373         foreach($this->settings as $setting) {
1374             $fullname = $setting->get_full_name();
1375             if (array_key_exists($fullname, $adminroot->errors)) {
1376                 $data = $adminroot->errors[$fullname]->data;
1377             } else {
1378                 $data = $setting->get_setting();
1379                 // do not use defaults if settings not available - upgrade settings handles the defaults!
1380             }
1381             $return .= $setting->output_html($data);
1382         }
1383         $return .= '</fieldset>';
1384         return $return;
1385     }
1387     /**
1388      * Is this settings page hidden in admin tree block?
1389      *
1390      * @return bool True if hidden
1391      */
1392     public function is_hidden() {
1393         return $this->hidden;
1394     }
1396     /**
1397      * Show we display Save button at the page bottom?
1398      * @return bool
1399      */
1400     public function show_save() {
1401         foreach($this->settings as $setting) {
1402             if (empty($setting->nosave)) {
1403                 return true;
1404             }
1405         }
1406         return false;
1407     }
1411 /**
1412  * Admin settings class. Only exists on setting pages.
1413  * Read & write happens at this level; no authentication.
1414  *
1415  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1416  */
1417 abstract class admin_setting {
1418     /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1419     public $name;
1420     /** @var string localised name */
1421     public $visiblename;
1422     /** @var string localised long description in Markdown format */
1423     public $description;
1424     /** @var mixed Can be string or array of string */
1425     public $defaultsetting;
1426     /** @var string */
1427     public $updatedcallback;
1428     /** @var mixed can be String or Null.  Null means main config table */
1429     public $plugin; // null means main config table
1430     /** @var bool true indicates this setting does not actually save anything, just information */
1431     public $nosave = false;
1432     /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1433     public $affectsmodinfo = false;
1435     /**
1436      * Constructor
1437      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1438      *                     or 'myplugin/mysetting' for ones in config_plugins.
1439      * @param string $visiblename localised name
1440      * @param string $description localised long description
1441      * @param mixed $defaultsetting string or array depending on implementation
1442      */
1443     public function __construct($name, $visiblename, $description, $defaultsetting) {
1444         $this->parse_setting_name($name);
1445         $this->visiblename    = $visiblename;
1446         $this->description    = $description;
1447         $this->defaultsetting = $defaultsetting;
1448     }
1450     /**
1451      * Set up $this->name and potentially $this->plugin
1452      *
1453      * Set up $this->name and possibly $this->plugin based on whether $name looks
1454      * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1455      * on the names, that is, output a developer debug warning if the name
1456      * contains anything other than [a-zA-Z0-9_]+.
1457      *
1458      * @param string $name the setting name passed in to the constructor.
1459      */
1460     private function parse_setting_name($name) {
1461         $bits = explode('/', $name);
1462         if (count($bits) > 2) {
1463             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1464         }
1465         $this->name = array_pop($bits);
1466         if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1467             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1468         }
1469         if (!empty($bits)) {
1470             $this->plugin = array_pop($bits);
1471             if ($this->plugin === 'moodle') {
1472                 $this->plugin = null;
1473             } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1474                     throw new moodle_exception('invalidadminsettingname', '', '', $name);
1475                 }
1476         }
1477     }
1479     /**
1480      * Returns the fullname prefixed by the plugin
1481      * @return string
1482      */
1483     public function get_full_name() {
1484         return 's_'.$this->plugin.'_'.$this->name;
1485     }
1487     /**
1488      * Returns the ID string based on plugin and name
1489      * @return string
1490      */
1491     public function get_id() {
1492         return 'id_s_'.$this->plugin.'_'.$this->name;
1493     }
1495     /**
1496      * @param bool $affectsmodinfo If true, changes to this setting will
1497      *   cause the course cache to be rebuilt
1498      */
1499     public function set_affects_modinfo($affectsmodinfo) {
1500         $this->affectsmodinfo = $affectsmodinfo;
1501     }
1503     /**
1504      * Returns the config if possible
1505      *
1506      * @return mixed returns config if successful else null
1507      */
1508     public function config_read($name) {
1509         global $CFG;
1510         if (!empty($this->plugin)) {
1511             $value = get_config($this->plugin, $name);
1512             return $value === false ? NULL : $value;
1514         } else {
1515             if (isset($CFG->$name)) {
1516                 return $CFG->$name;
1517             } else {
1518                 return NULL;
1519             }
1520         }
1521     }
1523     /**
1524      * Used to set a config pair and log change
1525      *
1526      * @param string $name
1527      * @param mixed $value Gets converted to string if not null
1528      * @return bool Write setting to config table
1529      */
1530     public function config_write($name, $value) {
1531         global $DB, $USER, $CFG;
1533         if ($this->nosave) {
1534             return true;
1535         }
1537         // make sure it is a real change
1538         $oldvalue = get_config($this->plugin, $name);
1539         $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1540         $value = is_null($value) ? null : (string)$value;
1542         if ($oldvalue === $value) {
1543             return true;
1544         }
1546         // store change
1547         set_config($name, $value, $this->plugin);
1549         // Some admin settings affect course modinfo
1550         if ($this->affectsmodinfo) {
1551             // Clear course cache for all courses
1552             rebuild_course_cache(0, true);
1553         }
1555         // log change
1556         $log = new stdClass();
1557         $log->userid       = during_initial_install() ? 0 :$USER->id; // 0 as user id during install
1558         $log->timemodified = time();
1559         $log->plugin       = $this->plugin;
1560         $log->name         = $name;
1561         $log->value        = $value;
1562         $log->oldvalue     = $oldvalue;
1563         $DB->insert_record('config_log', $log);
1565         return true; // BC only
1566     }
1568     /**
1569      * Returns current value of this setting
1570      * @return mixed array or string depending on instance, NULL means not set yet
1571      */
1572     public abstract function get_setting();
1574     /**
1575      * Returns default setting if exists
1576      * @return mixed array or string depending on instance; NULL means no default, user must supply
1577      */
1578     public function get_defaultsetting() {
1579         $adminroot =  admin_get_root(false, false);
1580         if (!empty($adminroot->custom_defaults)) {
1581             $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1582             if (isset($adminroot->custom_defaults[$plugin])) {
1583                 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
1584                     return $adminroot->custom_defaults[$plugin][$this->name];
1585                 }
1586             }
1587         }
1588         return $this->defaultsetting;
1589     }
1591     /**
1592      * Store new setting
1593      *
1594      * @param mixed $data string or array, must not be NULL
1595      * @return string empty string if ok, string error message otherwise
1596      */
1597     public abstract function write_setting($data);
1599     /**
1600      * Return part of form with setting
1601      * This function should always be overwritten
1602      *
1603      * @param mixed $data array or string depending on setting
1604      * @param string $query
1605      * @return string
1606      */
1607     public function output_html($data, $query='') {
1608     // should be overridden
1609         return;
1610     }
1612     /**
1613      * Function called if setting updated - cleanup, cache reset, etc.
1614      * @param string $functionname Sets the function name
1615      * @return void
1616      */
1617     public function set_updatedcallback($functionname) {
1618         $this->updatedcallback = $functionname;
1619     }
1621     /**
1622      * Is setting related to query text - used when searching
1623      * @param string $query
1624      * @return bool
1625      */
1626     public function is_related($query) {
1627         if (strpos(strtolower($this->name), $query) !== false) {
1628             return true;
1629         }
1630         if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
1631             return true;
1632         }
1633         if (strpos(textlib::strtolower($this->description), $query) !== false) {
1634             return true;
1635         }
1636         $current = $this->get_setting();
1637         if (!is_null($current)) {
1638             if (is_string($current)) {
1639                 if (strpos(textlib::strtolower($current), $query) !== false) {
1640                     return true;
1641                 }
1642             }
1643         }
1644         $default = $this->get_defaultsetting();
1645         if (!is_null($default)) {
1646             if (is_string($default)) {
1647                 if (strpos(textlib::strtolower($default), $query) !== false) {
1648                     return true;
1649                 }
1650             }
1651         }
1652         return false;
1653     }
1657 /**
1658  * No setting - just heading and text.
1659  *
1660  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1661  */
1662 class admin_setting_heading extends admin_setting {
1664     /**
1665      * not a setting, just text
1666      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1667      * @param string $heading heading
1668      * @param string $information text in box
1669      */
1670     public function __construct($name, $heading, $information) {
1671         $this->nosave = true;
1672         parent::__construct($name, $heading, $information, '');
1673     }
1675     /**
1676      * Always returns true
1677      * @return bool Always returns true
1678      */
1679     public function get_setting() {
1680         return true;
1681     }
1683     /**
1684      * Always returns true
1685      * @return bool Always returns true
1686      */
1687     public function get_defaultsetting() {
1688         return true;
1689     }
1691     /**
1692      * Never write settings
1693      * @return string Always returns an empty string
1694      */
1695     public function write_setting($data) {
1696     // do not write any setting
1697         return '';
1698     }
1700     /**
1701      * Returns an HTML string
1702      * @return string Returns an HTML string
1703      */
1704     public function output_html($data, $query='') {
1705         global $OUTPUT;
1706         $return = '';
1707         if ($this->visiblename != '') {
1708             $return .= $OUTPUT->heading($this->visiblename, 3, 'main');
1709         }
1710         if ($this->description != '') {
1711             $return .= $OUTPUT->box(highlight($query, markdown_to_html($this->description)), 'generalbox formsettingheading');
1712         }
1713         return $return;
1714     }
1718 /**
1719  * The most flexibly setting, user is typing text
1720  *
1721  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1722  */
1723 class admin_setting_configtext extends admin_setting {
1725     /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
1726     public $paramtype;
1727     /** @var int default field size */
1728     public $size;
1730     /**
1731      * Config text constructor
1732      *
1733      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1734      * @param string $visiblename localised
1735      * @param string $description long localised info
1736      * @param string $defaultsetting
1737      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
1738      * @param int $size default field size
1739      */
1740     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
1741         $this->paramtype = $paramtype;
1742         if (!is_null($size)) {
1743             $this->size  = $size;
1744         } else {
1745             $this->size  = ($paramtype === PARAM_INT) ? 5 : 30;
1746         }
1747         parent::__construct($name, $visiblename, $description, $defaultsetting);
1748     }
1750     /**
1751      * Return the setting
1752      *
1753      * @return mixed returns config if successful else null
1754      */
1755     public function get_setting() {
1756         return $this->config_read($this->name);
1757     }
1759     public function write_setting($data) {
1760         if ($this->paramtype === PARAM_INT and $data === '') {
1761         // do not complain if '' used instead of 0
1762             $data = 0;
1763         }
1764         // $data is a string
1765         $validated = $this->validate($data);
1766         if ($validated !== true) {
1767             return $validated;
1768         }
1769         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1770     }
1772     /**
1773      * Validate data before storage
1774      * @param string data
1775      * @return mixed true if ok string if error found
1776      */
1777     public function validate($data) {
1778         // allow paramtype to be a custom regex if it is the form of /pattern/
1779         if (preg_match('#^/.*/$#', $this->paramtype)) {
1780             if (preg_match($this->paramtype, $data)) {
1781                 return true;
1782             } else {
1783                 return get_string('validateerror', 'admin');
1784             }
1786         } else if ($this->paramtype === PARAM_RAW) {
1787             return true;
1789         } else {
1790             $cleaned = clean_param($data, $this->paramtype);
1791             if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
1792                 return true;
1793             } else {
1794                 return get_string('validateerror', 'admin');
1795             }
1796         }
1797     }
1799     /**
1800      * Return an XHTML string for the setting
1801      * @return string Returns an XHTML string
1802      */
1803     public function output_html($data, $query='') {
1804         $default = $this->get_defaultsetting();
1806         return format_admin_setting($this, $this->visiblename,
1807         '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
1808         $this->description, true, '', $default, $query);
1809     }
1813 /**
1814  * General text area without html editor.
1815  *
1816  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1817  */
1818 class admin_setting_configtextarea extends admin_setting_configtext {
1819     private $rows;
1820     private $cols;
1822     /**
1823      * @param string $name
1824      * @param string $visiblename
1825      * @param string $description
1826      * @param mixed $defaultsetting string or array
1827      * @param mixed $paramtype
1828      * @param string $cols The number of columns to make the editor
1829      * @param string $rows The number of rows to make the editor
1830      */
1831     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1832         $this->rows = $rows;
1833         $this->cols = $cols;
1834         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1835     }
1837     /**
1838      * Returns an XHTML string for the editor
1839      *
1840      * @param string $data
1841      * @param string $query
1842      * @return string XHTML string for the editor
1843      */
1844     public function output_html($data, $query='') {
1845         $default = $this->get_defaultsetting();
1847         $defaultinfo = $default;
1848         if (!is_null($default) and $default !== '') {
1849             $defaultinfo = "\n".$default;
1850         }
1852         return format_admin_setting($this, $this->visiblename,
1853         '<div class="form-textarea" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1854         $this->description, true, '', $defaultinfo, $query);
1855     }
1859 /**
1860  * General text area with html editor.
1861  */
1862 class admin_setting_confightmleditor extends admin_setting_configtext {
1863     private $rows;
1864     private $cols;
1866     /**
1867      * @param string $name
1868      * @param string $visiblename
1869      * @param string $description
1870      * @param mixed $defaultsetting string or array
1871      * @param mixed $paramtype
1872      */
1873     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1874         $this->rows = $rows;
1875         $this->cols = $cols;
1876         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1877         editors_head_setup();
1878     }
1880     /**
1881      * Returns an XHTML string for the editor
1882      *
1883      * @param string $data
1884      * @param string $query
1885      * @return string XHTML string for the editor
1886      */
1887     public function output_html($data, $query='') {
1888         $default = $this->get_defaultsetting();
1890         $defaultinfo = $default;
1891         if (!is_null($default) and $default !== '') {
1892             $defaultinfo = "\n".$default;
1893         }
1895         $editor = editors_get_preferred_editor(FORMAT_HTML);
1896         $editor->use_editor($this->get_id(), array('noclean'=>true));
1898         return format_admin_setting($this, $this->visiblename,
1899         '<div class="form-textarea"><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1900         $this->description, true, '', $defaultinfo, $query);
1901     }
1905 /**
1906  * Password field, allows unmasking of password
1907  *
1908  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1909  */
1910 class admin_setting_configpasswordunmask extends admin_setting_configtext {
1911     /**
1912      * Constructor
1913      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1914      * @param string $visiblename localised
1915      * @param string $description long localised info
1916      * @param string $defaultsetting default password
1917      */
1918     public function __construct($name, $visiblename, $description, $defaultsetting) {
1919         parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
1920     }
1922     /**
1923      * Returns XHTML for the field
1924      * Writes Javascript into the HTML below right before the last div
1925      *
1926      * @todo Make javascript available through newer methods if possible
1927      * @param string $data Value for the field
1928      * @param string $query Passed as final argument for format_admin_setting
1929      * @return string XHTML field
1930      */
1931     public function output_html($data, $query='') {
1932         $id = $this->get_id();
1933         $unmask = get_string('unmaskpassword', 'form');
1934         $unmaskjs = '<script type="text/javascript">
1935 //<![CDATA[
1936 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
1938 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
1940 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
1942 var unmaskchb = document.createElement("input");
1943 unmaskchb.setAttribute("type", "checkbox");
1944 unmaskchb.setAttribute("id", "'.$id.'unmask");
1945 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
1946 unmaskdiv.appendChild(unmaskchb);
1948 var unmasklbl = document.createElement("label");
1949 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
1950 if (is_ie) {
1951   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
1952 } else {
1953   unmasklbl.setAttribute("for", "'.$id.'unmask");
1955 unmaskdiv.appendChild(unmasklbl);
1957 if (is_ie) {
1958   // ugly hack to work around the famous onchange IE bug
1959   unmaskchb.onclick = function() {this.blur();};
1960   unmaskdiv.onclick = function() {this.blur();};
1962 //]]>
1963 </script>';
1964         return format_admin_setting($this, $this->visiblename,
1965         '<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>',
1966         $this->description, true, '', NULL, $query);
1967     }
1971 /**
1972  * Path to directory
1973  *
1974  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1975  */
1976 class admin_setting_configfile extends admin_setting_configtext {
1977     /**
1978      * Constructor
1979      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1980      * @param string $visiblename localised
1981      * @param string $description long localised info
1982      * @param string $defaultdirectory default directory location
1983      */
1984     public function __construct($name, $visiblename, $description, $defaultdirectory) {
1985         parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
1986     }
1988     /**
1989      * Returns XHTML for the field
1990      *
1991      * Returns XHTML for the field and also checks whether the file
1992      * specified in $data exists using file_exists()
1993      *
1994      * @param string $data File name and path to use in value attr
1995      * @param string $query
1996      * @return string XHTML field
1997      */
1998     public function output_html($data, $query='') {
1999         $default = $this->get_defaultsetting();
2001         if ($data) {
2002             if (file_exists($data)) {
2003                 $executable = '<span class="pathok">&#x2714;</span>';
2004             } else {
2005                 $executable = '<span class="patherror">&#x2718;</span>';
2006             }
2007         } else {
2008             $executable = '';
2009         }
2011         return format_admin_setting($this, $this->visiblename,
2012         '<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>',
2013         $this->description, true, '', $default, $query);
2014     }
2018 /**
2019  * Path to executable file
2020  *
2021  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2022  */
2023 class admin_setting_configexecutable extends admin_setting_configfile {
2025     /**
2026      * Returns an XHTML field
2027      *
2028      * @param string $data This is the value for the field
2029      * @param string $query
2030      * @return string XHTML field
2031      */
2032     public function output_html($data, $query='') {
2033         $default = $this->get_defaultsetting();
2035         if ($data) {
2036             if (file_exists($data) and is_executable($data)) {
2037                 $executable = '<span class="pathok">&#x2714;</span>';
2038             } else {
2039                 $executable = '<span class="patherror">&#x2718;</span>';
2040             }
2041         } else {
2042             $executable = '';
2043         }
2045         return format_admin_setting($this, $this->visiblename,
2046         '<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>',
2047         $this->description, true, '', $default, $query);
2048     }
2052 /**
2053  * Path to directory
2054  *
2055  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2056  */
2057 class admin_setting_configdirectory extends admin_setting_configfile {
2059     /**
2060      * Returns an XHTML field
2061      *
2062      * @param string $data This is the value for the field
2063      * @param string $query
2064      * @return string XHTML
2065      */
2066     public function output_html($data, $query='') {
2067         $default = $this->get_defaultsetting();
2069         if ($data) {
2070             if (file_exists($data) and is_dir($data)) {
2071                 $executable = '<span class="pathok">&#x2714;</span>';
2072             } else {
2073                 $executable = '<span class="patherror">&#x2718;</span>';
2074             }
2075         } else {
2076             $executable = '';
2077         }
2079         return format_admin_setting($this, $this->visiblename,
2080         '<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>',
2081         $this->description, true, '', $default, $query);
2082     }
2086 /**
2087  * Checkbox
2088  *
2089  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2090  */
2091 class admin_setting_configcheckbox extends admin_setting {
2092     /** @var string Value used when checked */
2093     public $yes;
2094     /** @var string Value used when not checked */
2095     public $no;
2097     /**
2098      * Constructor
2099      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2100      * @param string $visiblename localised
2101      * @param string $description long localised info
2102      * @param string $defaultsetting
2103      * @param string $yes value used when checked
2104      * @param string $no value used when not checked
2105      */
2106     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2107         parent::__construct($name, $visiblename, $description, $defaultsetting);
2108         $this->yes = (string)$yes;
2109         $this->no  = (string)$no;
2110     }
2112     /**
2113      * Retrieves the current setting using the objects name
2114      *
2115      * @return string
2116      */
2117     public function get_setting() {
2118         return $this->config_read($this->name);
2119     }
2121     /**
2122      * Sets the value for the setting
2123      *
2124      * Sets the value for the setting to either the yes or no values
2125      * of the object by comparing $data to yes
2126      *
2127      * @param mixed $data Gets converted to str for comparison against yes value
2128      * @return string empty string or error
2129      */
2130     public function write_setting($data) {
2131         if ((string)$data === $this->yes) { // convert to strings before comparison
2132             $data = $this->yes;
2133         } else {
2134             $data = $this->no;
2135         }
2136         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2137     }
2139     /**
2140      * Returns an XHTML checkbox field
2141      *
2142      * @param string $data If $data matches yes then checkbox is checked
2143      * @param string $query
2144      * @return string XHTML field
2145      */
2146     public function output_html($data, $query='') {
2147         $default = $this->get_defaultsetting();
2149         if (!is_null($default)) {
2150             if ((string)$default === $this->yes) {
2151                 $defaultinfo = get_string('checkboxyes', 'admin');
2152             } else {
2153                 $defaultinfo = get_string('checkboxno', 'admin');
2154             }
2155         } else {
2156             $defaultinfo = NULL;
2157         }
2159         if ((string)$data === $this->yes) { // convert to strings before comparison
2160             $checked = 'checked="checked"';
2161         } else {
2162             $checked = '';
2163         }
2165         return format_admin_setting($this, $this->visiblename,
2166         '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2167             .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2168         $this->description, true, '', $defaultinfo, $query);
2169     }
2173 /**
2174  * Multiple checkboxes, each represents different value, stored in csv format
2175  *
2176  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2177  */
2178 class admin_setting_configmulticheckbox extends admin_setting {
2179     /** @var array Array of choices value=>label */
2180     public $choices;
2182     /**
2183      * Constructor: uses parent::__construct
2184      *
2185      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2186      * @param string $visiblename localised
2187      * @param string $description long localised info
2188      * @param array $defaultsetting array of selected
2189      * @param array $choices array of $value=>$label for each checkbox
2190      */
2191     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2192         $this->choices = $choices;
2193         parent::__construct($name, $visiblename, $description, $defaultsetting);
2194     }
2196     /**
2197      * This public function may be used in ancestors for lazy loading of choices
2198      *
2199      * @todo Check if this function is still required content commented out only returns true
2200      * @return bool true if loaded, false if error
2201      */
2202     public function load_choices() {
2203         /*
2204         if (is_array($this->choices)) {
2205             return true;
2206         }
2207         .... load choices here
2208         */
2209         return true;
2210     }
2212     /**
2213      * Is setting related to query text - used when searching
2214      *
2215      * @param string $query
2216      * @return bool true on related, false on not or failure
2217      */
2218     public function is_related($query) {
2219         if (!$this->load_choices() or empty($this->choices)) {
2220             return false;
2221         }
2222         if (parent::is_related($query)) {
2223             return true;
2224         }
2226         foreach ($this->choices as $desc) {
2227             if (strpos(textlib::strtolower($desc), $query) !== false) {
2228                 return true;
2229             }
2230         }
2231         return false;
2232     }
2234     /**
2235      * Returns the current setting if it is set
2236      *
2237      * @return mixed null if null, else an array
2238      */
2239     public function get_setting() {
2240         $result = $this->config_read($this->name);
2242         if (is_null($result)) {
2243             return NULL;
2244         }
2245         if ($result === '') {
2246             return array();
2247         }
2248         $enabled = explode(',', $result);
2249         $setting = array();
2250         foreach ($enabled as $option) {
2251             $setting[$option] = 1;
2252         }
2253         return $setting;
2254     }
2256     /**
2257      * Saves the setting(s) provided in $data
2258      *
2259      * @param array $data An array of data, if not array returns empty str
2260      * @return mixed empty string on useless data or bool true=success, false=failed
2261      */
2262     public function write_setting($data) {
2263         if (!is_array($data)) {
2264             return ''; // ignore it
2265         }
2266         if (!$this->load_choices() or empty($this->choices)) {
2267             return '';
2268         }
2269         unset($data['xxxxx']);
2270         $result = array();
2271         foreach ($data as $key => $value) {
2272             if ($value and array_key_exists($key, $this->choices)) {
2273                 $result[] = $key;
2274             }
2275         }
2276         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2277     }
2279     /**
2280      * Returns XHTML field(s) as required by choices
2281      *
2282      * Relies on data being an array should data ever be another valid vartype with
2283      * acceptable value this may cause a warning/error
2284      * if (!is_array($data)) would fix the problem
2285      *
2286      * @todo Add vartype handling to ensure $data is an array
2287      *
2288      * @param array $data An array of checked values
2289      * @param string $query
2290      * @return string XHTML field
2291      */
2292     public function output_html($data, $query='') {
2293         if (!$this->load_choices() or empty($this->choices)) {
2294             return '';
2295         }
2296         $default = $this->get_defaultsetting();
2297         if (is_null($default)) {
2298             $default = array();
2299         }
2300         if (is_null($data)) {
2301             $data = array();
2302         }
2303         $options = array();
2304         $defaults = array();
2305         foreach ($this->choices as $key=>$description) {
2306             if (!empty($data[$key])) {
2307                 $checked = 'checked="checked"';
2308             } else {
2309                 $checked = '';
2310             }
2311             if (!empty($default[$key])) {
2312                 $defaults[] = $description;
2313             }
2315             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
2316                 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
2317         }
2319         if (is_null($default)) {
2320             $defaultinfo = NULL;
2321         } else if (!empty($defaults)) {
2322                 $defaultinfo = implode(', ', $defaults);
2323             } else {
2324                 $defaultinfo = get_string('none');
2325             }
2327         $return = '<div class="form-multicheckbox">';
2328         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2329         if ($options) {
2330             $return .= '<ul>';
2331             foreach ($options as $option) {
2332                 $return .= '<li>'.$option.'</li>';
2333             }
2334             $return .= '</ul>';
2335         }
2336         $return .= '</div>';
2338         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2340     }
2344 /**
2345  * Multiple checkboxes 2, value stored as string 00101011
2346  *
2347  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2348  */
2349 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
2351     /**
2352      * Returns the setting if set
2353      *
2354      * @return mixed null if not set, else an array of set settings
2355      */
2356     public function get_setting() {
2357         $result = $this->config_read($this->name);
2358         if (is_null($result)) {
2359             return NULL;
2360         }
2361         if (!$this->load_choices()) {
2362             return NULL;
2363         }
2364         $result = str_pad($result, count($this->choices), '0');
2365         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2366         $setting = array();
2367         foreach ($this->choices as $key=>$unused) {
2368             $value = array_shift($result);
2369             if ($value) {
2370                 $setting[$key] = 1;
2371             }
2372         }
2373         return $setting;
2374     }
2376     /**
2377      * Save setting(s) provided in $data param
2378      *
2379      * @param array $data An array of settings to save
2380      * @return mixed empty string for bad data or bool true=>success, false=>error
2381      */
2382     public function write_setting($data) {
2383         if (!is_array($data)) {
2384             return ''; // ignore it
2385         }
2386         if (!$this->load_choices() or empty($this->choices)) {
2387             return '';
2388         }
2389         $result = '';
2390         foreach ($this->choices as $key=>$unused) {
2391             if (!empty($data[$key])) {
2392                 $result .= '1';
2393             } else {
2394                 $result .= '0';
2395             }
2396         }
2397         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2398     }
2402 /**
2403  * Select one value from list
2404  *
2405  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2406  */
2407 class admin_setting_configselect extends admin_setting {
2408     /** @var array Array of choices value=>label */
2409     public $choices;
2411     /**
2412      * Constructor
2413      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2414      * @param string $visiblename localised
2415      * @param string $description long localised info
2416      * @param string|int $defaultsetting
2417      * @param array $choices array of $value=>$label for each selection
2418      */
2419     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2420         $this->choices = $choices;
2421         parent::__construct($name, $visiblename, $description, $defaultsetting);
2422     }
2424     /**
2425      * This function may be used in ancestors for lazy loading of choices
2426      *
2427      * Override this method if loading of choices is expensive, such
2428      * as when it requires multiple db requests.
2429      *
2430      * @return bool true if loaded, false if error
2431      */
2432     public function load_choices() {
2433         /*
2434         if (is_array($this->choices)) {
2435             return true;
2436         }
2437         .... load choices here
2438         */
2439         return true;
2440     }
2442     /**
2443      * Check if this is $query is related to a choice
2444      *
2445      * @param string $query
2446      * @return bool true if related, false if not
2447      */
2448     public function is_related($query) {
2449         if (parent::is_related($query)) {
2450             return true;
2451         }
2452         if (!$this->load_choices()) {
2453             return false;
2454         }
2455         foreach ($this->choices as $key=>$value) {
2456             if (strpos(textlib::strtolower($key), $query) !== false) {
2457                 return true;
2458             }
2459             if (strpos(textlib::strtolower($value), $query) !== false) {
2460                 return true;
2461             }
2462         }
2463         return false;
2464     }
2466     /**
2467      * Return the setting
2468      *
2469      * @return mixed returns config if successful else null
2470      */
2471     public function get_setting() {
2472         return $this->config_read($this->name);
2473     }
2475     /**
2476      * Save a setting
2477      *
2478      * @param string $data
2479      * @return string empty of error string
2480      */
2481     public function write_setting($data) {
2482         if (!$this->load_choices() or empty($this->choices)) {
2483             return '';
2484         }
2485         if (!array_key_exists($data, $this->choices)) {
2486             return ''; // ignore it
2487         }
2489         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2490     }
2492     /**
2493      * Returns XHTML select field
2494      *
2495      * Ensure the options are loaded, and generate the XHTML for the select
2496      * element and any warning message. Separating this out from output_html
2497      * makes it easier to subclass this class.
2498      *
2499      * @param string $data the option to show as selected.
2500      * @param string $current the currently selected option in the database, null if none.
2501      * @param string $default the default selected option.
2502      * @return array the HTML for the select element, and a warning message.
2503      */
2504     public function output_select_html($data, $current, $default, $extraname = '') {
2505         if (!$this->load_choices() or empty($this->choices)) {
2506             return array('', '');
2507         }
2509         $warning = '';
2510         if (is_null($current)) {
2511         // first run
2512         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2513             // no warning
2514             } else if (!array_key_exists($current, $this->choices)) {
2515                     $warning = get_string('warningcurrentsetting', 'admin', s($current));
2516                     if (!is_null($default) and $data == $current) {
2517                         $data = $default; // use default instead of first value when showing the form
2518                     }
2519                 }
2521         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
2522         foreach ($this->choices as $key => $value) {
2523         // the string cast is needed because key may be integer - 0 is equal to most strings!
2524             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
2525         }
2526         $selecthtml .= '</select>';
2527         return array($selecthtml, $warning);
2528     }
2530     /**
2531      * Returns XHTML select field and wrapping div(s)
2532      *
2533      * @see output_select_html()
2534      *
2535      * @param string $data the option to show as selected
2536      * @param string $query
2537      * @return string XHTML field and wrapping div
2538      */
2539     public function output_html($data, $query='') {
2540         $default = $this->get_defaultsetting();
2541         $current = $this->get_setting();
2543         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2544         if (!$selecthtml) {
2545             return '';
2546         }
2548         if (!is_null($default) and array_key_exists($default, $this->choices)) {
2549             $defaultinfo = $this->choices[$default];
2550         } else {
2551             $defaultinfo = NULL;
2552         }
2554         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
2556         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
2557     }
2561 /**
2562  * Select multiple items from list
2563  *
2564  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2565  */
2566 class admin_setting_configmultiselect extends admin_setting_configselect {
2567     /**
2568      * Constructor
2569      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2570      * @param string $visiblename localised
2571      * @param string $description long localised info
2572      * @param array $defaultsetting array of selected items
2573      * @param array $choices array of $value=>$label for each list item
2574      */
2575     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2576         parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
2577     }
2579     /**
2580      * Returns the select setting(s)
2581      *
2582      * @return mixed null or array. Null if no settings else array of setting(s)
2583      */
2584     public function get_setting() {
2585         $result = $this->config_read($this->name);
2586         if (is_null($result)) {
2587             return NULL;
2588         }
2589         if ($result === '') {
2590             return array();
2591         }
2592         return explode(',', $result);
2593     }
2595     /**
2596      * Saves setting(s) provided through $data
2597      *
2598      * Potential bug in the works should anyone call with this function
2599      * using a vartype that is not an array
2600      *
2601      * @param array $data
2602      */
2603     public function write_setting($data) {
2604         if (!is_array($data)) {
2605             return ''; //ignore it
2606         }
2607         if (!$this->load_choices() or empty($this->choices)) {
2608             return '';
2609         }
2611         unset($data['xxxxx']);
2613         $save = array();
2614         foreach ($data as $value) {
2615             if (!array_key_exists($value, $this->choices)) {
2616                 continue; // ignore it
2617             }
2618             $save[] = $value;
2619         }
2621         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2622     }
2624     /**
2625      * Is setting related to query text - used when searching
2626      *
2627      * @param string $query
2628      * @return bool true if related, false if not
2629      */
2630     public function is_related($query) {
2631         if (!$this->load_choices() or empty($this->choices)) {
2632             return false;
2633         }
2634         if (parent::is_related($query)) {
2635             return true;
2636         }
2638         foreach ($this->choices as $desc) {
2639             if (strpos(textlib::strtolower($desc), $query) !== false) {
2640                 return true;
2641             }
2642         }
2643         return false;
2644     }
2646     /**
2647      * Returns XHTML multi-select field
2648      *
2649      * @todo Add vartype handling to ensure $data is an array
2650      * @param array $data Array of values to select by default
2651      * @param string $query
2652      * @return string XHTML multi-select field
2653      */
2654     public function output_html($data, $query='') {
2655         if (!$this->load_choices() or empty($this->choices)) {
2656             return '';
2657         }
2658         $choices = $this->choices;
2659         $default = $this->get_defaultsetting();
2660         if (is_null($default)) {
2661             $default = array();
2662         }
2663         if (is_null($data)) {
2664             $data = array();
2665         }
2667         $defaults = array();
2668         $size = min(10, count($this->choices));
2669         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2670         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
2671         foreach ($this->choices as $key => $description) {
2672             if (in_array($key, $data)) {
2673                 $selected = 'selected="selected"';
2674             } else {
2675                 $selected = '';
2676             }
2677             if (in_array($key, $default)) {
2678                 $defaults[] = $description;
2679             }
2681             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2682         }
2684         if (is_null($default)) {
2685             $defaultinfo = NULL;
2686         } if (!empty($defaults)) {
2687             $defaultinfo = implode(', ', $defaults);
2688         } else {
2689             $defaultinfo = get_string('none');
2690         }
2692         $return .= '</select></div>';
2693         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
2694     }
2697 /**
2698  * Time selector
2699  *
2700  * This is a liiitle bit messy. we're using two selects, but we're returning
2701  * them as an array named after $name (so we only use $name2 internally for the setting)
2702  *
2703  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2704  */
2705 class admin_setting_configtime extends admin_setting {
2706     /** @var string Used for setting second select (minutes) */
2707     public $name2;
2709     /**
2710      * Constructor
2711      * @param string $hoursname setting for hours
2712      * @param string $minutesname setting for hours
2713      * @param string $visiblename localised
2714      * @param string $description long localised info
2715      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2716      */
2717     public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
2718         $this->name2 = $minutesname;
2719         parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
2720     }
2722     /**
2723      * Get the selected time
2724      *
2725      * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
2726      */
2727     public function get_setting() {
2728         $result1 = $this->config_read($this->name);
2729         $result2 = $this->config_read($this->name2);
2730         if (is_null($result1) or is_null($result2)) {
2731             return NULL;
2732         }
2734         return array('h' => $result1, 'm' => $result2);
2735     }
2737     /**
2738      * Store the time (hours and minutes)
2739      *
2740      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2741      * @return bool true if success, false if not
2742      */
2743     public function write_setting($data) {
2744         if (!is_array($data)) {
2745             return '';
2746         }
2748         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2749         return ($result ? '' : get_string('errorsetting', 'admin'));
2750     }
2752     /**
2753      * Returns XHTML time select fields
2754      *
2755      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2756      * @param string $query
2757      * @return string XHTML time select fields and wrapping div(s)
2758      */
2759     public function output_html($data, $query='') {
2760         $default = $this->get_defaultsetting();
2762         if (is_array($default)) {
2763             $defaultinfo = $default['h'].':'.$default['m'];
2764         } else {
2765             $defaultinfo = NULL;
2766         }
2768         $return = '<div class="form-time defaultsnext">'.
2769             '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
2770         for ($i = 0; $i < 24; $i++) {
2771             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2772         }
2773         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2774         for ($i = 0; $i < 60; $i += 5) {
2775             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2776         }
2777         $return .= '</select></div>';
2778         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2779     }
2784 /**
2785  * Seconds duration setting.
2786  *
2787  * @copyright 2012 Petr Skoda (http://skodak.org)
2788  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2789  */
2790 class admin_setting_configduration extends admin_setting {
2792     /** @var int default duration unit */
2793     protected $defaultunit;
2795     /**
2796      * Constructor
2797      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
2798      *                     or 'myplugin/mysetting' for ones in config_plugins.
2799      * @param string $visiblename localised name
2800      * @param string $description localised long description
2801      * @param mixed $defaultsetting string or array depending on implementation
2802      * @param int $defaultunit - day, week, etc. (in seconds)
2803      */
2804     public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
2805         if (is_number($defaultsetting)) {
2806             $defaultsetting = self::parse_seconds($defaultsetting);
2807         }
2808         $units = self::get_units();
2809         if (isset($units[$defaultunit])) {
2810             $this->defaultunit = $defaultunit;
2811         } else {
2812             $this->defaultunit = 86400;
2813         }
2814         parent::__construct($name, $visiblename, $description, $defaultsetting);
2815     }
2817     /**
2818      * Returns selectable units.
2819      * @static
2820      * @return array
2821      */
2822     protected static function get_units() {
2823         return array(
2824             604800 => get_string('weeks'),
2825             86400 => get_string('days'),
2826             3600 => get_string('hours'),
2827             60 => get_string('minutes'),
2828             1 => get_string('seconds'),
2829         );
2830     }
2832     /**
2833      * Converts seconds to some more user friendly string.
2834      * @static
2835      * @param int $seconds
2836      * @return string
2837      */
2838     protected static function get_duration_text($seconds) {
2839         if (empty($seconds)) {
2840             return get_string('none');
2841         }
2842         $data = self::parse_seconds($seconds);
2843         switch ($data['u']) {
2844             case (60*60*24*7):
2845                 return get_string('numweeks', '', $data['v']);
2846             case (60*60*24):
2847                 return get_string('numdays', '', $data['v']);
2848             case (60*60):
2849                 return get_string('numhours', '', $data['v']);
2850             case (60):
2851                 return get_string('numminutes', '', $data['v']);
2852             default:
2853                 return get_string('numseconds', '', $data['v']*$data['u']);
2854         }
2855     }
2857     /**
2858      * Finds suitable units for given duration.
2859      * @static
2860      * @param int $seconds
2861      * @return array
2862      */
2863     protected static function parse_seconds($seconds) {
2864         foreach (self::get_units() as $unit => $unused) {
2865             if ($seconds % $unit === 0) {
2866                 return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
2867             }
2868         }
2869         return array('v'=>(int)$seconds, 'u'=>1);
2870     }
2872     /**
2873      * Get the selected duration as array.
2874      *
2875      * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
2876      */
2877     public function get_setting() {
2878         $seconds = $this->config_read($this->name);
2879         if (is_null($seconds)) {
2880             return null;
2881         }
2883         return self::parse_seconds($seconds);
2884     }
2886     /**
2887      * Store the duration as seconds.
2888      *
2889      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2890      * @return bool true if success, false if not
2891      */
2892     public function write_setting($data) {
2893         if (!is_array($data)) {
2894             return '';
2895         }
2897         $seconds = (int)($data['v']*$data['u']);
2898         if ($seconds < 0) {
2899             return get_string('errorsetting', 'admin');
2900         }
2902         $result = $this->config_write($this->name, $seconds);
2903         return ($result ? '' : get_string('errorsetting', 'admin'));
2904     }
2906     /**
2907      * Returns duration text+select fields.
2908      *
2909      * @param array $data Must be form 'v'=>xx, 'u'=>xx
2910      * @param string $query
2911      * @return string duration text+select fields and wrapping div(s)
2912      */
2913     public function output_html($data, $query='') {
2914         $default = $this->get_defaultsetting();
2916         if (is_number($default)) {
2917             $defaultinfo = self::get_duration_text($default);
2918         } else if (is_array($default)) {
2919             $defaultinfo = self::get_duration_text($default['v']*$default['u']);
2920         } else {
2921             $defaultinfo = null;
2922         }
2924         $units = self::get_units();
2926         $return = '<div class="form-duration defaultsnext">';
2927         $return .= '<input type="text" size="5" id="'.$this->get_id().'v" name="'.$this->get_full_name().'[v]" value="'.s($data['v']).'" />';
2928         $return .= '<select id="'.$this->get_id().'u" name="'.$this->get_full_name().'[u]">';
2929         foreach ($units as $val => $text) {
2930             $selected = '';
2931             if ($data['v'] == 0) {
2932                 if ($val == $this->defaultunit) {
2933                     $selected = ' selected="selected"';
2934                 }
2935             } else if ($val == $data['u']) {
2936                 $selected = ' selected="selected"';
2937             }
2938             $return .= '<option value="'.$val.'"'.$selected.'>'.$text.'</option>';
2939         }
2940         $return .= '</select></div>';
2941         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2942     }
2946 /**
2947  * Used to validate a textarea used for ip addresses
2948  *
2949  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2950  */
2951 class admin_setting_configiplist extends admin_setting_configtextarea {
2953     /**
2954      * Validate the contents of the textarea as IP addresses
2955      *
2956      * Used to validate a new line separated list of IP addresses collected from
2957      * a textarea control
2958      *
2959      * @param string $data A list of IP Addresses separated by new lines
2960      * @return mixed bool true for success or string:error on failure
2961      */
2962     public function validate($data) {
2963         if(!empty($data)) {
2964             $ips = explode("\n", $data);
2965         } else {
2966             return true;
2967         }
2968         $result = true;
2969         foreach($ips as $ip) {
2970             $ip = trim($ip);
2971             if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
2972                 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
2973                 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
2974                 $result = true;
2975             } else {
2976                 $result = false;
2977                 break;
2978             }
2979         }
2980         if($result) {
2981             return true;
2982         } else {
2983             return get_string('validateerror', 'admin');
2984         }
2985     }
2989 /**
2990  * An admin setting for selecting one or more users who have a capability
2991  * in the system context
2992  *
2993  * An admin setting for selecting one or more users, who have a particular capability
2994  * in the system context. Warning, make sure the list will never be too long. There is
2995  * no paging or searching of this list.
2996  *
2997  * To correctly get a list of users from this config setting, you need to call the
2998  * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
2999  *
3000  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3001  */
3002 class admin_setting_users_with_capability extends admin_setting_configmultiselect {
3003     /** @var string The capabilities name */
3004     protected $capability;
3005     /** @var int include admin users too */
3006     protected $includeadmins;
3008     /**
3009      * Constructor.
3010      *
3011      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3012      * @param string $visiblename localised name
3013      * @param string $description localised long description
3014      * @param array $defaultsetting array of usernames
3015      * @param string $capability string capability name.
3016      * @param bool $includeadmins include administrators
3017      */
3018     function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
3019         $this->capability    = $capability;
3020         $this->includeadmins = $includeadmins;
3021         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
3022     }
3024     /**
3025      * Load all of the uses who have the capability into choice array
3026      *
3027      * @return bool Always returns true
3028      */
3029     function load_choices() {
3030         if (is_array($this->choices)) {
3031             return true;
3032         }
3033         list($sort, $sortparams) = users_order_by_sql('u');
3034         if (!empty($sortparams)) {
3035             throw new coding_exception('users_order_by_sql returned some query parameters. ' .
3036                     'This is unexpected, and a problem because there is no way to pass these ' .
3037                     'parameters to get_users_by_capability. See MDL-34657.');
3038         }
3039         $users = get_users_by_capability(context_system::instance(),
3040                 $this->capability, 'u.id,u.username,u.firstname,u.lastname', $sort);
3041         $this->choices = array(
3042             '$@NONE@$' => get_string('nobody'),
3043             '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
3044         );
3045         if ($this->includeadmins) {
3046             $admins = get_admins();
3047             foreach ($admins as $user) {
3048                 $this->choices[$user->id] = fullname($user);
3049             }
3050         }
3051         if (is_array($users)) {
3052             foreach ($users as $user) {
3053                 $this->choices[$user->id] = fullname($user);
3054             }
3055         }
3056         return true;
3057     }
3059     /**
3060      * Returns the default setting for class
3061      *
3062      * @return mixed Array, or string. Empty string if no default
3063      */
3064     public function get_defaultsetting() {
3065         $this->load_choices();
3066         $defaultsetting = parent::get_defaultsetting();
3067         if (empty($defaultsetting)) {
3068             return array('$@NONE@$');
3069         } else if (array_key_exists($defaultsetting, $this->choices)) {
3070                 return $defaultsetting;
3071             } else {
3072                 return '';
3073             }
3074     }
3076     /**
3077      * Returns the current setting
3078      *
3079      * @return mixed array or string
3080      */
3081     public function get_setting() {
3082         $result = parent::get_setting();
3083         if ($result === null) {
3084             // this is necessary for settings upgrade
3085             return null;
3086         }
3087         if (empty($result)) {
3088             $result = array('$@NONE@$');
3089         }
3090         return $result;
3091     }
3093     /**
3094      * Save the chosen setting provided as $data
3095      *
3096      * @param array $data
3097      * @return mixed string or array
3098      */
3099     public function write_setting($data) {
3100     // If all is selected, remove any explicit options.
3101         if (in_array('$@ALL@$', $data)) {
3102             $data = array('$@ALL@$');
3103         }
3104         // None never needs to be written to the DB.
3105         if (in_array('$@NONE@$', $data)) {
3106             unset($data[array_search('$@NONE@$', $data)]);
3107         }
3108         return parent::write_setting($data);
3109     }
3113 /**
3114  * Special checkbox for calendar - resets SESSION vars.
3115  *
3116  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3117  */
3118 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
3119     /**
3120      * Calls the parent::__construct with default values
3121      *
3122      * name =>  calendar_adminseesall
3123      * visiblename => get_string('adminseesall', 'admin')
3124      * description => get_string('helpadminseesall', 'admin')
3125      * defaultsetting => 0
3126      */
3127     public function __construct() {
3128         parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
3129             get_string('helpadminseesall', 'admin'), '0');
3130     }
3132     /**
3133      * Stores the setting passed in $data
3134      *
3135      * @param mixed gets converted to string for comparison
3136      * @return string empty string or error message
3137      */
3138     public function write_setting($data) {
3139         global $SESSION;
3140         return parent::write_setting($data);
3141     }
3144 /**
3145  * Special select for settings that are altered in setup.php and can not be altered on the fly
3146  *
3147  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3148  */
3149 class admin_setting_special_selectsetup extends admin_setting_configselect {
3150     /**
3151      * Reads the setting directly from the database
3152      *
3153      * @return mixed
3154      */
3155     public function get_setting() {
3156     // read directly from db!
3157         return get_config(NULL, $this->name);
3158     }
3160     /**
3161      * Save the setting passed in $data
3162      *
3163      * @param string $data The setting to save
3164      * @return string empty or error message
3165      */
3166     public function write_setting($data) {
3167         global $CFG;
3168         // do not change active CFG setting!
3169         $current = $CFG->{$this->name};
3170         $result = parent::write_setting($data);
3171         $CFG->{$this->name} = $current;
3172         return $result;
3173     }
3177 /**
3178  * Special select for frontpage - stores data in course table
3179  *
3180  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3181  */
3182 class admin_setting_sitesetselect extends admin_setting_configselect {
3183     /**
3184      * Returns the site name for the selected site
3185      *
3186      * @see get_site()
3187      * @return string The site name of the selected site
3188      */
3189     public function get_setting() {
3190         $site = course_get_format(get_site())->get_course();
3191         return $site->{$this->name};
3192     }
3194     /**
3195      * Updates the database and save the setting
3196      *
3197      * @param string data
3198      * @return string empty or error message
3199      */
3200     public function write_setting($data) {
3201         global $DB, $SITE;
3202         if (!in_array($data, array_keys($this->choices))) {
3203             return get_string('errorsetting', 'admin');
3204         }
3205         $record = new stdClass();
3206         $record->id           = SITEID;
3207         $temp                 = $this->name;
3208         $record->$temp        = $data;
3209         $record->timemodified = time();
3210         // update $SITE
3211         $SITE->{$this->name} = $data;
3212         course_get_format($SITE)->update_course_format_options($record);
3213         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3214     }
3218 /**
3219  * Select for blog's bloglevel setting: if set to 0, will set blog_menu
3220  * block to hidden.
3221  *
3222  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3223  */
3224 class admin_setting_bloglevel extends admin_setting_configselect {
3225     /**
3226      * Updates the database and save the setting
3227      *
3228      * @param string data
3229      * @return string empty or error message
3230      */
3231     public function write_setting($data) {
3232         global $DB, $CFG;
3233         if ($data == 0) {
3234             $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
3235             foreach ($blogblocks as $block) {
3236                 $DB->set_field('block', 'visible', 0, array('id' => $block->id));
3237             }
3238         } else {
3239             // reenable all blocks only when switching from disabled blogs
3240             if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
3241                 $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
3242                 foreach ($blogblocks as $block) {
3243                     $DB->set_field('block', 'visible', 1, array('id' => $block->id));
3244                 }
3245             }
3246         }
3247         return parent::write_setting($data);
3248     }
3252 /**
3253  * Special select - lists on the frontpage - hacky
3254  *
3255  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3256  */
3257 class admin_setting_courselist_frontpage extends admin_setting {
3258     /** @var array Array of choices value=>label */
3259     public $choices;
3261     /**
3262      * Construct override, requires one param
3263      *
3264      * @param bool $loggedin Is the user logged in
3265      */
3266     public function __construct($loggedin) {
3267         global $CFG;
3268         require_once($CFG->dirroot.'/course/lib.php');
3269         $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
3270         $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
3271         $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
3272         $defaults    = array(FRONTPAGECOURSELIST);
3273         parent::__construct($name, $visiblename, $description, $defaults);
3274     }
3276     /**
3277      * Loads the choices available
3278      *
3279      * @return bool always returns true
3280      */
3281     public function load_choices() {
3282         global $DB;
3283         if (is_array($this->choices)) {
3284             return true;
3285         }
3286         $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
3287             FRONTPAGECOURSELIST    => get_string('frontpagecourselist'),
3288             FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
3289             FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
3290             'none'                 => get_string('none'));
3291         if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
3292             unset($this->choices[FRONTPAGECOURSELIST]);
3293         }
3294         return true;
3295     }
3297     /**
3298      * Returns the selected settings
3299      *
3300      * @param mixed array or setting or null
3301      */
3302     public function get_setting() {
3303         $result = $this->config_read($this->name);
3304         if (is_null($result)) {
3305             return NULL;
3306         }
3307         if ($result === '') {
3308             return array();
3309         }
3310         return explode(',', $result);
3311     }
3313     /**
3314      * Save the selected options
3315      *
3316      * @param array $data
3317      * @return mixed empty string (data is not an array) or bool true=success false=failure
3318      */
3319     public function write_setting($data) {
3320         if (!is_array($data)) {
3321             return '';
3322         }
3323         $this->load_choices();
3324         $save = array();
3325         foreach($data as $datum) {
3326             if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
3327                 continue;
3328             }
3329             $save[$datum] = $datum; // no duplicates
3330         }
3331         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3332     }
3334     /**
3335      * Return XHTML select field and wrapping div
3336      *
3337      * @todo Add vartype handling to make sure $data is an array
3338      * @param array $data Array of elements to select by default
3339      * @return string XHTML select field and wrapping div
3340      */
3341     public function output_html($data, $query='') {
3342         $this->load_choices();
3343         $currentsetting = array();
3344         foreach ($data as $key) {
3345             if ($key != 'none' and array_key_exists($key, $this->choices)) {
3346                 $currentsetting[] = $key; // already selected first
3347             }
3348         }
3350         $return = '<div class="form-group">';
3351         for ($i = 0; $i < count($this->choices) - 1; $i++) {
3352             if (!array_key_exists($i, $currentsetting)) {
3353                 $currentsetting[$i] = 'none'; //none
3354             }
3355             $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
3356             foreach ($this->choices as $key => $value) {
3357                 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
3358             }
3359             $return .= '</select>';
3360             if ($i !== count($this->choices) - 2) {
3361                 $return .= '<br />';
3362             }
3363         }
3364         $return .= '</div>';
3366         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3367     }
3371 /**
3372  * Special checkbox for frontpage - stores data in course table
3373  *
3374  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3375  */
3376 class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
3377     /**
3378      * Returns the current sites name
3379      *
3380      * @return string
3381      */
3382     public function get_setting() {
3383         $site = course_get_format(get_site())->get_course();
3384         return $site->{$this->name};
3385     }
3387     /**
3388      * Save the selected setting
3389      *
3390      * @param string $data The selected site
3391      * @return string empty string or error message
3392      */
3393     public function write_setting($data) {
3394         global $DB, $SITE;
3395         $record = new stdClass();
3396         $record->id            = $SITE->id;
3397         $record->{$this->name} = ($data == '1' ? 1 : 0);
3398         $record->timemodified  = time();
3399         // update $SITE
3400         $SITE->{$this->name} = $data;
3401         course_get_format($SITE)->update_course_format_options($record);
3402         $DB->update_record('course', $record);
3403         // There is something wrong in cache updates somewhere, let's reset everything.
3404         format_base::reset_course_cache();
3405         return '';
3406     }
3409 /**
3410  * Special text for frontpage - stores data in course table.
3411  * Empty string means not set here. Manual setting is required.
3412  *
3413  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3414  */
3415 class admin_setting_sitesettext extends admin_setting_configtext {
3416     /**
3417      * Return the current setting
3418      *
3419      * @return mixed string or null
3420      */
3421     public function get_setting() {
3422         $site = course_get_format(get_site())->get_course();
3423         return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
3424     }
3426     /**
3427      * Validate the selected data
3428      *
3429      * @param string $data The selected value to validate
3430      * @return mixed true or message string
3431      */
3432     public function validate($data) {
3433         $cleaned = clean_param($data, PARAM_TEXT);
3434         if ($cleaned === '') {
3435             return get_string('required');
3436         }
3437         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
3438             return true;
3439         } else {
3440             return get_string('validateerror', 'admin');
3441         }
3442     }
3444     /**
3445      * Save the selected setting
3446      *
3447      * @param string $data The selected value
3448      * @return string empty or error message
3449      */
3450     public function write_setting($data) {
3451         global $DB, $SITE;
3452         $data = trim($data);
3453         $validated = $this->validate($data);
3454         if ($validated !== true) {
3455             return $validated;
3456         }
3458         $record = new stdClass();
3459         $record->id            = $SITE->id;
3460         $record->{$this->name} = $data;
3461         $record->timemodified  = time();
3462         // update $SITE
3463         $SITE->{$this->name} = $data;
3464         course_get_format($SITE)->update_course_format_options($record);
3465         $DB->update_record('course', $record);
3466         // There is something wrong in cache updates somewhere, let's reset everything.
3467         format_base::reset_course_cache();
3468         return '';
3469     }
3473 /**
3474  * Special text editor for site description.
3475  *
3476  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3477  */
3478 class admin_setting_special_frontpagedesc extends admin_setting {
3479     /**
3480      * Calls parent::__construct with specific arguments
3481      */
3482     public function __construct() {
3483         parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
3484         editors_head_setup();
3485     }
3487     /**
3488      * Return the current setting
3489      * @return string The current setting
3490      */
3491     public function get_setting() {
3492         $site = course_get_format(get_site())->get_course();
3493         return $site->{$this->name};
3494     }
3496     /**
3497      * Save the new setting
3498      *
3499      * @param string $data The new value to save
3500      * @return string empty or error message
3501      */
3502     public function write_setting($data) {
3503         global $DB, $SITE;
3504         $record = new stdClass();
3505         $record->id            = $SITE->id;
3506         $record->{$this->name} = $data;
3507         $record->timemodified  = time();
3508         $SITE->{$this->name} = $data;
3509         course_get_format($SITE)->update_course_format_options($record);
3510         $DB->update_record('course', $record);
3511         // There is something wrong in cache updates somewhere, let's reset everything.
3512         format_base::reset_course_cache();
3513         return '';
3514     }
3516     /**
3517      * Returns XHTML for the field plus wrapping div
3518      *
3519      * @param string $data The current value
3520      * @param string $query
3521      * @return string The XHTML output
3522      */
3523     public function output_html($data, $query='') {
3524         global $CFG;
3526         $CFG->adminusehtmleditor = can_use_html_editor();
3527         $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
3529         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3530     }
3534 /**
3535  * Administration interface for emoticon_manager settings.
3536  *
3537  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3538  */
3539 class admin_setting_emoticons extends admin_setting {
3541     /**
3542      * Calls parent::__construct with specific args
3543      */
3544     public function __construct() {
3545         global $CFG;
3547         $manager = get_emoticon_manager();
3548         $defaults = $this->prepare_form_data($manager->default_emoticons());
3549         parent::__construct('emoticons', get_string('emoticons', 'admin'), get_string('emoticons_desc', 'admin'), $defaults);
3550     }
3552     /**
3553      * Return the current setting(s)
3554      *
3555      * @return array Current settings array
3556      */
3557     public function get_setting() {
3558         global $CFG;
3560         $manager = get_emoticon_manager();
3562         $config = $this->config_read($this->name);
3563         if (is_null($config)) {
3564             return null;
3565         }
3567         $config = $manager->decode_stored_config($config);
3568         if (is_null($config)) {
3569             return null;
3570         }
3572         return $this->prepare_form_data($config);
3573     }
3575     /**
3576      * Save selected settings
3577      *
3578      * @param array $data Array of settings to save
3579      * @return bool
3580      */
3581     public function write_setting($data) {
3583         $manager = get_emoticon_manager();
3584         $emoticons = $this->process_form_data($data);
3586         if ($emoticons === false) {
3587             return false;
3588         }
3590         if ($this->config_write($this->name, $manager->encode_stored_config($emoticons))) {
3591             return ''; // success
3592         } else {
3593             return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
3594         }
3595     }
3597     /**
3598      * Return XHTML field(s) for options
3599      *
3600      * @param array $data Array of options to set in HTML
3601      * @return string XHTML string for the fields and wrapping div(s)
3602      */
3603     public function output_html($data, $query='') {
3604         global $OUTPUT;
3606         $out  = html_writer::start_tag('table', array('border' => 1, 'class' => 'generaltable'));
3607         $out .= html_writer::start_tag('thead');
3608         $out .= html_writer::start_tag('tr');
3609         $out .= html_writer::tag('th', get_string('emoticontext', 'admin'));
3610         $out .= html_writer::tag('th', get_string('emoticonimagename', 'admin'));
3611         $out .= html_writer::tag('th', get_string('emoticoncomponent', 'admin'));
3612         $out .= html_writer::tag('th', get_string('emoticonalt', 'admin'), array('colspan' => 2));
3613         $out .= html_writer::tag('th', '');
3614         $out .= html_writer::end_tag('tr');
3615         $out .= html_writer::end_tag('thead');
3616         $out .= html_writer::start_tag('tbody');
3617         $i = 0;
3618         foreach($data as $field => $value) {
3619             switch ($i) {
3620             case 0:
3621                 $out .= html_writer::start_tag('tr');
3622                 $current_text = $value;
3623                 $current_filename = '';
3624                 $current_imagecomponent = '';
3625                 $current_altidentifier = '';
3626                 $current_altcomponent = '';
3627             case 1:
3628                 $current_filename = $value;
3629             case 2:
3630                 $current_imagecomponent = $value;
3631             case 3:
3632                 $current_altidentifier = $value;
3633             case 4:
3634                 $current_altcomponent = $value;
3635             }
3637             $out .= html_writer::tag('td',
3638                 html_writer::empty_tag('input',
3639                     array(
3640                         'type'  => 'text',
3641                         'class' => 'form-text',
3642                         'name'  => $this->get_full_name().'['.$field.']',
3643                         'value' => $value,
3644                     )
3645                 ), array('class' => 'c'.$i)
3646             );
3648             if ($i == 4) {
3649                 if (get_string_manager()->string_exists($current_altidentifier, $current_altcomponent)) {
3650                     $alt = get_string($current_altidentifier, $current_altcomponent);
3651                 } else {
3652                     $alt = $current_text;
3653                 }
3654                 if ($current_filename) {
3655                     $out .= html_writer::tag('td', $OUTPUT->render(new pix_emoticon($current_filename, $alt, $current_imagecomponent)));
3656                 } else {
3657                     $out .= html_writer::tag('td', '');
3658                 }
3659                 $out .= html_writer::end_tag('tr');
3660                 $i = 0;
3661             } else {
3662                 $i++;
3663             }
3665         }
3666         $out .= html_writer::end_tag('tbody');
3667         $out .= html_writer::end_tag('table');
3668         $out  = html_writer::tag('div', $out, array('class' => 'form-group'));