Updated the HEAD build version to 20100321
[moodle.git] / lib / adminlib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Functions and classes used during installation, upgrades and for admin settings.
20  *
21  *  ADMIN SETTINGS TREE INTRODUCTION
22  *
23  *  This file performs the following tasks:
24  *   -it defines the necessary objects and interfaces to build the Moodle
25  *    admin hierarchy
26  *   -it defines the admin_externalpage_setup(), admin_externalpage_print_header(),
27  *    and admin_externalpage_print_footer() functions used on admin pages
28  *
29  *  ADMIN_SETTING OBJECTS
30  *
31  *  Moodle settings are represented by objects that inherit from the admin_setting
32  *  class. These objects encapsulate how to read a setting, how to write a new value
33  *  to a setting, and how to appropriately display the HTML to modify the setting.
34  *
35  *  ADMIN_SETTINGPAGE OBJECTS
36  *
37  *  The admin_setting objects are then grouped into admin_settingpages. The latter
38  *  appear in the Moodle admin tree block. All interaction with admin_settingpage
39  *  objects is handled by the admin/settings.php file.
40  *
41  *  ADMIN_EXTERNALPAGE OBJECTS
42  *
43  *  There are some settings in Moodle that are too complex to (efficiently) handle
44  *  with admin_settingpages. (Consider, for example, user management and displaying
45  *  lists of users.) In this case, we use the admin_externalpage object. This object
46  *  places a link to an external PHP file in the admin tree block.
47  *
48  *  If you're using an admin_externalpage object for some settings, you can take
49  *  advantage of the admin_externalpage_* functions. For example, suppose you wanted
50  *  to add a foo.php file into admin. First off, you add the following line to
51  *  admin/settings/first.php (at the end of the file) or to some other file in
52  *  admin/settings:
53  * <code>
54  *     $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
55  *         $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
56  * </code>
57  *
58  *  Next, in foo.php, your file structure would resemble the following:
59  * <code>
60  *         require_once('.../config.php');
61  *         require_once($CFG->libdir.'/adminlib.php');
62  *         admin_externalpage_setup('foo');
63  *         // functionality like processing form submissions goes here
64  *         $OUTPUT->header();
65  *         // your HTML goes here
66  *         $OUTPUT->footer();
67  * </code>
68  *
69  *  The admin_externalpage_setup() function call ensures the user is logged in,
70  *  and makes sure that they have the proper role permission to access the page.
71  *  It also configures all $PAGE properties needed for navigation.
72  *
73  *  ADMIN_CATEGORY OBJECTS
74  *
75  *  Above and beyond all this, we have admin_category objects. These objects
76  *  appear as folders in the admin tree block. They contain admin_settingpage's,
77  *  admin_externalpage's, and other admin_category's.
78  *
79  *  OTHER NOTES
80  *
81  *  admin_settingpage's, admin_externalpage's, and admin_category's all inherit
82  *  from part_of_admin_tree (a pseudointerface). This interface insists that
83  *  a class has a check_access method for access permissions, a locate method
84  *  used to find a specific node in the admin tree and find parent path.
85  *
86  *  admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
87  *  interface ensures that the class implements a recursive add function which
88  *  accepts a part_of_admin_tree object and searches for the proper place to
89  *  put it. parentable_part_of_admin_tree implies part_of_admin_tree.
90  *
91  *  Please note that the $this->name field of any part_of_admin_tree must be
92  *  UNIQUE throughout the ENTIRE admin tree.
93  *
94  *  The $this->name field of an admin_setting object (which is *not* part_of_
95  *  admin_tree) must be unique on the respective admin_settingpage where it is
96  *  used.
97  *
98  * Original author: Vincenzo K. Marcovecchio
99  * Maintainer:      Petr Skoda
100  *
101  * @package   moodlecore
102  * @copyright 1999 onwards Martin Dougiamas  http://dougiamas.com
103  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
104  */
106 /// Add libraries
107 require_once($CFG->libdir.'/ddllib.php');
108 require_once($CFG->libdir.'/xmlize.php');
110 define('INSECURE_DATAROOT_WARNING', 1);
111 define('INSECURE_DATAROOT_ERROR', 2);
113 /**
114  * Automatically clean-up all plugin data and remove the plugin DB tables
115  *
116  * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
117  * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
118  * @uses global $OUTPUT to produce notices and other messages
119  * @return void
120  */
121 function uninstall_plugin($type, $name) {
122     global $CFG, $DB, $OUTPUT;
124     // recursively uninstall all the subplugins first
125     $subpluginlocations = plugin_supports($type, $name, FEATURE_MOD_SUBPLUGINS);
126     if (is_array($subpluginlocations)) {
127         foreach ($subpluginlocations as $subplugintype => $notusedlocationpath) {
128             $subplugins = get_plugin_list($subplugintype);
129             foreach ($subplugins as $subpluginname => $notusedpluginpath) {
130                 uninstall_plugin($subplugintype, $subpluginname);
131             }
132         }
133     }
135     $component = $type . '_' . $name;  // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
137     if ($type === 'mod') {
138         $pluginname = $name;  // eg. 'forum'
139         $strpluginname = get_string('modulename', $pluginname);
140     } else {
141         $pluginname = $component;
142         $strpluginname = get_string('pluginname', $pluginname); // replaces string 'modulename'
143     }
144     echo $OUTPUT->heading($pluginname);
146     $plugindirectory = get_plugin_directory($type, $name);
147     $uninstalllib = $plugindirectory . '/db/uninstall.php';
148     if (file_exists($uninstalllib)) {
149         require_once($uninstalllib);
150         $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall';    // eg. 'xmldb_workshop_uninstall()'
151         if (function_exists($uninstallfunction)) {
152             if (!$uninstallfunction()) {
153                 echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $pluginname);
154             }
155         }
156     }
158     if ('mod' === $type) {
159     // perform cleanup tasks specific for activity modules
161         if (!$module = $DB->get_record('modules', array('name' => $name))) {
162             print_error('moduledoesnotexist', 'error');
163         }
165         // delete all the relevant instances from all course sections
166         if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) {
167             foreach ($coursemods as $coursemod) {
168                 if (!delete_mod_from_section($coursemod->id, $coursemod->section)) {
169                     echo $OUTPUT->notification("Could not delete the $strpluginname with id = $coursemod->id from section $coursemod->section");
170                 }
171             }
172         }
174         // clear course.modinfo for courses that used this module
175         $sql = "UPDATE {course}
176                    SET modinfo=''
177                  WHERE id IN (SELECT DISTINCT course
178                                 FROM {course_modules}
179                                WHERE module=?)";
180         $DB->execute($sql, array($module->id));
182         // delete all the course module records
183         $DB->delete_records('course_modules', array('module' => $module->id));
185         // delete module contexts
186         if ($coursemods) {
187             foreach ($coursemods as $coursemod) {
188                 if (!delete_context(CONTEXT_MODULE, $coursemod->id)) {
189                     echo $OUTPUT->notification("Could not delete the context for $strpluginname with id = $coursemod->id");
190                 }
191             }
192         }
194         // delete the module entry itself
195         $DB->delete_records('modules', array('name' => $module->name));
197         // cleanup the gradebook
198         require_once($CFG->libdir.'/gradelib.php');
199         grade_uninstalled_module($module->name);
201         // Perform any custom uninstall tasks
202         if (file_exists($CFG->dirroot . '/mod/' . $module->name . '/lib.php')) {
203             require_once($CFG->dirroot . '/mod/' . $module->name . '/lib.php');
204             $uninstallfunction = $module->name . '_uninstall';
205             if (function_exists($uninstallfunction)) {
206                 debugging("{$uninstallfunction}() has been deprecated. Use the plugin's db/uninstall.php instead", DEBUG_DEVELOPER);
207                 if (!$uninstallfunction()) {
208                     echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $module->name.'!');
209                 }
210             }
211         }
212     }
214     // perform clean-up taks common for all the plugin/subplugin types
216     // delete calendar events
217     $DB->delete_records('event', array('modulename' => $pluginname));
219     // delete all the logs
220     $DB->delete_records('log', array('module' => $pluginname));
222     // delete log_display information
223     $DB->delete_records('log_display', array('module' => $pluginname));
225     // delete the module configuration records
226     unset_all_config_for_plugin($pluginname);
228     // delete the plugin tables
229     $xmldbfilepath = $plugindirectory . '/db/install.xml';
230     drop_plugin_tables($pluginname, $xmldbfilepath, false);
232     // delete the capabilities that were defined by this module
233     capabilities_cleanup($component);
235     // remove entent handlers and dequeue pending events
236     events_uninstall($component);
238     echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
241 /**
242  * Delete all plugin tables
243  *
244  * @param string $name Name of plugin, used as table prefix
245  * @param string $file Path to install.xml file
246  * @param bool $feedback defaults to true
247  * @return bool Always returns true
248  */
249 function drop_plugin_tables($name, $file, $feedback=true) {
250     global $CFG, $DB;
252     // first try normal delete
253     if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
254         return true;
255     }
257     // then try to find all tables that start with name and are not in any xml file
258     $used_tables = get_used_table_names();
260     $tables = $DB->get_tables();
262     /// Iterate over, fixing id fields as necessary
263     foreach ($tables as $table) {
264         if (in_array($table, $used_tables)) {
265             continue;
266         }
268         if (strpos($table, $name) !== 0) {
269             continue;
270         }
272         // found orphan table --> delete it
273         if ($DB->get_manager()->table_exists($table)) {
274             $xmldb_table = new xmldb_table($table);
275             $DB->get_manager()->drop_table($xmldb_table);
276         }
277     }
279     return true;
282 /**
283  * Returns names of all known tables == tables that moodle knowns about.
284  *
285  * @return array Array of lowercase table names
286  */
287 function get_used_table_names() {
288     $table_names = array();
289     $dbdirs = get_db_directories();
291     foreach ($dbdirs as $dbdir) {
292         $file = $dbdir.'/install.xml';
294         $xmldb_file = new xmldb_file($file);
296         if (!$xmldb_file->fileExists()) {
297             continue;
298         }
300         $loaded    = $xmldb_file->loadXMLStructure();
301         $structure = $xmldb_file->getStructure();
303         if ($loaded and $tables = $structure->getTables()) {
304             foreach($tables as $table) {
305                 $table_names[] = strtolower($table->name);
306             }
307         }
308     }
310     return $table_names;
313 /**
314  * Returns list of all directories where we expect install.xml files
315  * @return array Array of paths
316  */
317 function get_db_directories() {
318     global $CFG;
320     $dbdirs = array();
322     /// First, the main one (lib/db)
323     $dbdirs[] = $CFG->libdir.'/db';
325     /// Then, all the ones defined by get_plugin_types()
326     $plugintypes = get_plugin_types();
327     foreach ($plugintypes as $plugintype => $pluginbasedir) {
328         if ($plugins = get_plugin_list($plugintype)) {
329             foreach ($plugins as $plugin => $plugindir) {
330                 $dbdirs[] = $plugindir.'/db';
331             }
332         }
333     }
335     return $dbdirs;
338 /**
339  * Try to obtain or release the cron lock.
340  * @param string  $name  name of lock
341  * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionaly
342  * @param bool $ignorecurrent ignore current lock state, usually entend previous lock, defaults to false
343  * @return bool true if lock obtained
344  */
345 function set_cron_lock($name, $until, $ignorecurrent=false) {
346     global $DB;
347     if (empty($name)) {
348         debugging("Tried to get a cron lock for a null fieldname");
349         return false;
350     }
352     // remove lock by force == remove from config table
353     if (is_null($until)) {
354         set_config($name, null);
355         return true;
356     }
358     if (!$ignorecurrent) {
359     // read value from db - other processes might have changed it
360         $value = $DB->get_field('config', 'value', array('name'=>$name));
362         if ($value and $value > time()) {
363         //lock active
364             return false;
365         }
366     }
368     set_config($name, $until);
369     return true;
372 /**
373  * Test if and critical warnings are present
374  * @return bool
375  */
376 function admin_critical_warnings_present() {
377     global $SESSION;
379     if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
380         return 0;
381     }
383     if (!isset($SESSION->admin_critical_warning)) {
384         $SESSION->admin_critical_warning = 0;
385         if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
386             $SESSION->admin_critical_warning = 1;
387         }
388     }
390     return $SESSION->admin_critical_warning;
393 /**
394  * Detects if float supports at least 10 decimal digits
395  *
396  * Detects if float supports at least 10 deciman digits
397  * and also if float-->string conversion works as expected.
398  *
399  * @return bool true if problem found
400  */
401 function is_float_problem() {
402     $num1 = 2009010200.01;
403     $num2 = 2009010200.02;
405     return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
408 /**
409  * Try to verify that dataroot is not accessible from web.
410  *
411  * Try to verify that dataroot is not accessible from web.
412  * It is not 100% correct but might help to reduce number of vulnerable sites.
413  * Protection from httpd.conf and .htaccess is not detected properly.
414  *
415  * @uses INSECURE_DATAROOT_WARNING
416  * @uses INSECURE_DATAROOT_ERROR
417  * @param bool $fetchtest try to test public access by fetching file, default false
418  * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING migth be problematic
419  */
420 function is_dataroot_insecure($fetchtest=false) {
421     global $CFG;
423     $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
425     $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
426     $rp = strrev(trim($rp, '/'));
427     $rp = explode('/', $rp);
428     foreach($rp as $r) {
429         if (strpos($siteroot, '/'.$r.'/') === 0) {
430             $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
431         } else {
432             break; // probably alias root
433         }
434     }
436     $siteroot = strrev($siteroot);
437     $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
439     if (strpos($dataroot, $siteroot) !== 0) {
440         return false;
441     }
443     if (!$fetchtest) {
444         return INSECURE_DATAROOT_WARNING;
445     }
447     // now try all methods to fetch a test file using http protocol
449     $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
450     preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
451     $httpdocroot = $matches[1];
452     $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
453     if (make_upload_directory('diag', false) === false) {
454         return INSECURE_DATAROOT_WARNING;
455     }
456     $testfile = $CFG->dataroot.'/diag/public.txt';
457     if (!file_exists($testfile)) {
458         file_put_contents($testfile, 'test file, do not delete');
459     }
460     $teststr = trim(file_get_contents($testfile));
461     if (empty($teststr)) {
462     // hmm, strange
463         return INSECURE_DATAROOT_WARNING;
464     }
466     $testurl = $datarooturl.'/diag/public.txt';
467     if (extension_loaded('curl') and
468         !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
469         !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
470         ($ch = @curl_init($testurl)) !== false) {
471         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
472         curl_setopt($ch, CURLOPT_HEADER, false);
473         $data = curl_exec($ch);
474         if (!curl_errno($ch)) {
475             $data = trim($data);
476             if ($data === $teststr) {
477                 curl_close($ch);
478                 return INSECURE_DATAROOT_ERROR;
479             }
480         }
481         curl_close($ch);
482     }
484     if ($data = @file_get_contents($testurl)) {
485         $data = trim($data);
486         if ($data === $teststr) {
487             return INSECURE_DATAROOT_ERROR;
488         }
489     }
491     preg_match('|https?://([^/]+)|i', $testurl, $matches);
492     $sitename = $matches[1];
493     $error = 0;
494     if ($fp = @fsockopen($sitename, 80, $error)) {
495         preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
496         $localurl = $matches[1];
497         $out = "GET $localurl HTTP/1.1\r\n";
498         $out .= "Host: $sitename\r\n";
499         $out .= "Connection: Close\r\n\r\n";
500         fwrite($fp, $out);
501         $data = '';
502         $incoming = false;
503         while (!feof($fp)) {
504             if ($incoming) {
505                 $data .= fgets($fp, 1024);
506             } else if (@fgets($fp, 1024) === "\r\n") {
507                     $incoming = true;
508                 }
509         }
510         fclose($fp);
511         $data = trim($data);
512         if ($data === $teststr) {
513             return INSECURE_DATAROOT_ERROR;
514         }
515     }
517     return INSECURE_DATAROOT_WARNING;
520 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
522 /**
523  * Pseudointerface for anything appearing in the admin tree
524  *
525  * The pseudointerface that is implemented by anything that appears in the admin tree
526  * block. It forces inheriting classes to define a method for checking user permissions
527  * and methods for finding something in the admin tree.
528  *
529  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
530  */
531 interface part_of_admin_tree {
533 /**
534  * Finds a named part_of_admin_tree.
535  *
536  * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
537  * and not parentable_part_of_admin_tree, then this function should only check if
538  * $this->name matches $name. If it does, it should return a reference to $this,
539  * otherwise, it should return a reference to NULL.
540  *
541  * If a class inherits parentable_part_of_admin_tree, this method should be called
542  * recursively on all child objects (assuming, of course, the parent object's name
543  * doesn't match the search criterion).
544  *
545  * @param string $name The internal name of the part_of_admin_tree we're searching for.
546  * @return mixed An object reference or a NULL reference.
547  */
548     public function locate($name);
550     /**
551      * Removes named part_of_admin_tree.
552      *
553      * @param string $name The internal name of the part_of_admin_tree we want to remove.
554      * @return bool success.
555      */
556     public function prune($name);
558     /**
559      * Search using query
560      * @param string $query
561      * @return mixed array-object structure of found settings and pages
562      */
563     public function search($query);
565     /**
566      * Verifies current user's access to this part_of_admin_tree.
567      *
568      * Used to check if the current user has access to this part of the admin tree or
569      * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
570      * then this method is usually just a call to has_capability() in the site context.
571      *
572      * If a class inherits parentable_part_of_admin_tree, this method should return the
573      * logical OR of the return of check_access() on all child objects.
574      *
575      * @return bool True if the user has access, false if she doesn't.
576      */
577     public function check_access();
579     /**
580      * Mostly usefull for removing of some parts of the tree in admin tree block.
581      *
582      * @return True is hidden from normal list view
583      */
584     public function is_hidden();
586     /**
587      * Show we display Save button at the page bottom?
588      * @return bool
589      */
590     public function show_save();
593 /**
594  * Pseudointerface implemented by any part_of_admin_tree that has children.
595  *
596  * The pseudointerface implemented by any part_of_admin_tree that can be a parent
597  * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
598  * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
599  * include an add method for adding other part_of_admin_tree objects as children.
600  *
601  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
602  */
603 interface parentable_part_of_admin_tree extends part_of_admin_tree {
605 /**
606  * Adds a part_of_admin_tree object to the admin tree.
607  *
608  * Used to add a part_of_admin_tree object to this object or a child of this
609  * object. $something should only be added if $destinationname matches
610  * $this->name. If it doesn't, add should be called on child objects that are
611  * also parentable_part_of_admin_tree's.
612  *
613  * @param string $destinationname The internal name of the new parent for $something.
614  * @param part_of_admin_tree $something The object to be added.
615  * @return bool True on success, false on failure.
616  */
617     public function add($destinationname, $something);
621 /**
622  * The object used to represent folders (a.k.a. categories) in the admin tree block.
623  *
624  * Each admin_category object contains a number of part_of_admin_tree objects.
625  *
626  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
627  */
628 class admin_category implements parentable_part_of_admin_tree {
630 /** @var mixed An array of part_of_admin_tree objects that are this object's children */
631     public $children;
632     /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
633     public $name;
634     /** @var string The displayed name for this category. Usually obtained through get_string() */
635     public $visiblename;
636     /** @var bool Should this category be hidden in admin tree block? */
637     public $hidden;
638     /** @var mixed Either a string or an array or strings */
639     public $path;
640     /** @var mixed Either a string or an array or strings */
641     public $visiblepath;
643     /**
644      * Constructor for an empty admin category
645      *
646      * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
647      * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
648      * @param bool $hidden hide category in admin tree block, defaults to false
649      */
650     public function __construct($name, $visiblename, $hidden=false) {
651         $this->children    = array();
652         $this->name        = $name;
653         $this->visiblename = $visiblename;
654         $this->hidden      = $hidden;
655     }
657     /**
658      * Returns a reference to the part_of_admin_tree object with internal name $name.
659      *
660      * @param string $name The internal name of the object we want.
661      * @param bool $findpath initialize path and visiblepath arrays
662      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
663      *                  defaults to false
664      */
665     public function locate($name, $findpath=false) {
666         if ($this->name == $name) {
667             if ($findpath) {
668                 $this->visiblepath[] = $this->visiblename;
669                 $this->path[]        = $this->name;
670             }
671             return $this;
672         }
674         $return = NULL;
675         foreach($this->children as $childid=>$unused) {
676             if ($return = $this->children[$childid]->locate($name, $findpath)) {
677                 break;
678             }
679         }
681         if (!is_null($return) and $findpath) {
682             $return->visiblepath[] = $this->visiblename;
683             $return->path[]        = $this->name;
684         }
686         return $return;
687     }
689     /**
690      * Search using query
691      *
692      * @param string query
693      * @return mixed array-object structure of found settings and pages
694      */
695     public function search($query) {
696         $result = array();
697         foreach ($this->children as $child) {
698             $subsearch = $child->search($query);
699             if (!is_array($subsearch)) {
700                 debugging('Incorrect search result from '.$child->name);
701                 continue;
702             }
703             $result = array_merge($result, $subsearch);
704         }
705         return $result;
706     }
708     /**
709      * Removes part_of_admin_tree object with internal name $name.
710      *
711      * @param string $name The internal name of the object we want to remove.
712      * @return bool success
713      */
714     public function prune($name) {
716         if ($this->name == $name) {
717             return false;  //can not remove itself
718         }
720         foreach($this->children as $precedence => $child) {
721             if ($child->name == $name) {
722             // found it!
723                 unset($this->children[$precedence]);
724                 return true;
725             }
726             if ($this->children[$precedence]->prune($name)) {
727                 return true;
728             }
729         }
730         return false;
731     }
733     /**
734      * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
735      *
736      * @param string $destinationame The internal name of the immediate parent that we want for $something.
737      * @param mixed $something A part_of_admin_tree or setting instanceto be added.
738      * @return bool True if successfully added, false if $something can not be added.
739      */
740     public function add($parentname, $something) {
741         $parent = $this->locate($parentname);
742         if (is_null($parent)) {
743             debugging('parent does not exist!');
744             return false;
745         }
747         if ($something instanceof part_of_admin_tree) {
748             if (!($parent instanceof parentable_part_of_admin_tree)) {
749                 debugging('error - parts of tree can be inserted only into parentable parts');
750                 return false;
751             }
752             $parent->children[] = $something;
753             return true;
755         } else {
756             debugging('error - can not add this element');
757             return false;
758         }
760     }
762     /**
763      * Checks if the user has access to anything in this category.
764      *
765      * @return bool True if the user has access to atleast one child in this category, false otherwise.
766      */
767     public function check_access() {
768         foreach ($this->children as $child) {
769             if ($child->check_access()) {
770                 return true;
771             }
772         }
773         return false;
774     }
776     /**
777      * Is this category hidden in admin tree block?
778      *
779      * @return bool True if hidden
780      */
781     public function is_hidden() {
782         return $this->hidden;
783     }
785     /**
786      * Show we display Save button at the page bottom?
787      * @return bool
788      */
789     public function show_save() {
790         foreach ($this->children as $child) {
791             if ($child->show_save()) {
792                 return true;
793             }
794         }
795         return false;
796     }
799 /**
800  * Root of admin settings tree, does not have any parent.
801  *
802  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
803  */
804 class admin_root extends admin_category {
805 /** @var array List of errors */
806     public $errors;
807     /** @var string search query */
808     public $search;
809     /** @var bool full tree flag - true means all settings required, false onlypages required */
810     public $fulltree;
811     /** @var bool flag indicating loaded tree */
812     public $loaded;
813     /** @var mixed site custom defaults overriding defaults in setings files*/
814     public $custom_defaults;
816     /**
817      * @param bool $fulltree true means all settings required,
818      *                            false only pages required
819      */
820     public function __construct($fulltree) {
821         global $CFG;
823         parent::__construct('root', get_string('administration'), false);
824         $this->errors   = array();
825         $this->search   = '';
826         $this->fulltree = $fulltree;
827         $this->loaded   = false;
829         // load custom defaults if found
830         $this->custom_defaults = null;
831         $defaultsfile = "$CFG->dirroot/local/defaults.php";
832         if (is_readable($defaultsfile)) {
833             $defaults = array();
834             include($defaultsfile);
835             if (is_array($defaults) and count($defaults)) {
836                 $this->custom_defaults = $defaults;
837             }
838         }
839     }
841     /**
842      * Empties children array, and sets loaded to false
843      *
844      * @param bool $requirefulltree
845      */
846     public function purge_children($requirefulltree) {
847         $this->children = array();
848         $this->fulltree = ($requirefulltree || $this->fulltree);
849         $this->loaded   = false;
850     }
853 /**
854  * Links external PHP pages into the admin tree.
855  *
856  * See detailed usage example at the top of this document (adminlib.php)
857  *
858  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
859  */
860 class admin_externalpage implements part_of_admin_tree {
862 /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
863     public $name;
865     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
866     public $visiblename;
868     /** @var string The external URL that we should link to when someone requests this external page. */
869     public $url;
871     /** @var string The role capability/permission a user must have to access this external page. */
872     public $req_capability;
874     /** @var object The context in which capability/permission should be checked, default is site context. */
875     public $context;
877     /** @var bool hidden in admin tree block. */
878     public $hidden;
880     /** @var mixed either string or array of string */
881     public $path;
882     public $visiblepath;
884     /**
885      * Constructor for adding an external page into the admin tree.
886      *
887      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
888      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
889      * @param string $url The external URL that we should link to when someone requests this external page.
890      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
891      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
892      * @param context $context The context the page relates to. Not sure what happens
893      *      if you specify something other than system or front page. Defaults to system.
894      */
895     public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
896         $this->name        = $name;
897         $this->visiblename = $visiblename;
898         $this->url         = $url;
899         if (is_array($req_capability)) {
900             $this->req_capability = $req_capability;
901         } else {
902             $this->req_capability = array($req_capability);
903         }
904         $this->hidden = $hidden;
905         $this->context = $context;
906     }
908     /**
909      * Returns a reference to the part_of_admin_tree object with internal name $name.
910      *
911      * @param string $name The internal name of the object we want.
912      * @param bool $findpath defaults to false
913      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
914      */
915     public function locate($name, $findpath=false) {
916         if ($this->name == $name) {
917             if ($findpath) {
918                 $this->visiblepath = array($this->visiblename);
919                 $this->path        = array($this->name);
920             }
921             return $this;
922         } else {
923             $return = NULL;
924             return $return;
925         }
926     }
928     /**
929      * This function always returns false, required function by interface
930      *
931      * @param string $name
932      * @return false
933      */
934     public function prune($name) {
935         return false;
936     }
938     /**
939      * Search using query
940      *
941      * @param string $query
942      * @return mixed array-object structure of found settings and pages
943      */
944     public function search($query) {
945         $textlib = textlib_get_instance();
947         $found = false;
948         if (strpos(strtolower($this->name), $query) !== false) {
949             $found = true;
950         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
951                 $found = true;
952             }
953         if ($found) {
954             $result = new object();
955             $result->page     = $this;
956             $result->settings = array();
957             return array($this->name => $result);
958         } else {
959             return array();
960         }
961     }
963     /**
964      * Determines if the current user has access to this external page based on $this->req_capability.
965      *
966      * @return bool True if user has access, false otherwise.
967      */
968     public function check_access() {
969         global $CFG;
970         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
971         foreach($this->req_capability as $cap) {
972             if (is_valid_capability($cap) and has_capability($cap, $context)) {
973                 return true;
974             }
975         }
976         return false;
977     }
979     /**
980      * Is this external page hidden in admin tree block?
981      *
982      * @return bool True if hidden
983      */
984     public function is_hidden() {
985         return $this->hidden;
986     }
988     /**
989      * Show we display Save button at the page bottom?
990      * @return bool
991      */
992     public function show_save() {
993         return false;
994     }
997 /**
998  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
999  *
1000  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1001  */
1002 class admin_settingpage implements part_of_admin_tree {
1004 /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
1005     public $name;
1007     /** @var string The displayed name for this external page. Usually obtained through get_string(). */
1008     public $visiblename;
1010     /** @var mixed An array of admin_setting objects that are part of this setting page. */
1011     public $settings;
1013     /** @var string The role capability/permission a user must have to access this external page. */
1014     public $req_capability;
1016     /** @var object The context in which capability/permission should be checked, default is site context. */
1017     public $context;
1019     /** @var bool hidden in admin tree block. */
1020     public $hidden;
1022     /** @var mixed string of paths or array of strings of paths */
1023     public $path;
1024     public $visiblepath;
1026     /**
1027      * see admin_settingpage for details of this function
1028      *
1029      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1030      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1031      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1032      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1033      * @param context $context The context the page relates to. Not sure what happens
1034      *      if you specify something other than system or front page. Defaults to system.
1035      */
1036     public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1037         $this->settings    = new object();
1038         $this->name        = $name;
1039         $this->visiblename = $visiblename;
1040         if (is_array($req_capability)) {
1041             $this->req_capability = $req_capability;
1042         } else {
1043             $this->req_capability = array($req_capability);
1044         }
1045         $this->hidden      = $hidden;
1046         $this->context     = $context;
1047     }
1049     /**
1050      * see admin_category
1051      *
1052      * @param string $name
1053      * @param bool $findpath
1054      * @return mixed Object (this) if name ==  this->name, else returns null
1055      */
1056     public function locate($name, $findpath=false) {
1057         if ($this->name == $name) {
1058             if ($findpath) {
1059                 $this->visiblepath = array($this->visiblename);
1060                 $this->path        = array($this->name);
1061             }
1062             return $this;
1063         } else {
1064             $return = NULL;
1065             return $return;
1066         }
1067     }
1069     /**
1070      * Search string in settings page.
1071      *
1072      * @param string $query
1073      * @return array
1074      */
1075     public function search($query) {
1076         $found = array();
1078         foreach ($this->settings as $setting) {
1079             if ($setting->is_related($query)) {
1080                 $found[] = $setting;
1081             }
1082         }
1084         if ($found) {
1085             $result = new object();
1086             $result->page     = $this;
1087             $result->settings = $found;
1088             return array($this->name => $result);
1089         }
1091         $textlib = textlib_get_instance();
1093         $found = false;
1094         if (strpos(strtolower($this->name), $query) !== false) {
1095             $found = true;
1096         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1097                 $found = true;
1098             }
1099         if ($found) {
1100             $result = new object();
1101             $result->page     = $this;
1102             $result->settings = array();
1103             return array($this->name => $result);
1104         } else {
1105             return array();
1106         }
1107     }
1109     /**
1110      * This function always returns false, required by interface
1111      *
1112      * @param string $name
1113      * @return bool Always false
1114      */
1115     public function prune($name) {
1116         return false;
1117     }
1119     /**
1120      * adds an admin_setting to this admin_settingpage
1121      *
1122      * 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
1123      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1124      *
1125      * @param object $setting is the admin_setting object you want to add
1126      * @return bool true if successful, false if not
1127      */
1128     public function add($setting) {
1129         if (!($setting instanceof admin_setting)) {
1130             debugging('error - not a setting instance');
1131             return false;
1132         }
1134         $this->settings->{$setting->name} = $setting;
1135         return true;
1136     }
1138     /**
1139      * see admin_externalpage
1140      *
1141      * @return bool Returns true for yes false for no
1142      */
1143     public function check_access() {
1144         global $CFG;
1145         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
1146         foreach($this->req_capability as $cap) {
1147             if (is_valid_capability($cap) and has_capability($cap, $context)) {
1148                 return true;
1149             }
1150         }
1151         return false;
1152     }
1154     /**
1155      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1156      * @return string Returns an XHTML string
1157      */
1158     public function output_html() {
1159         $adminroot = admin_get_root();
1160         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1161         foreach($this->settings as $setting) {
1162             $fullname = $setting->get_full_name();
1163             if (array_key_exists($fullname, $adminroot->errors)) {
1164                 $data = $adminroot->errors[$fullname]->data;
1165             } else {
1166                 $data = $setting->get_setting();
1167             // do not use defaults if settings not available - upgrdesettings handles the defaults!
1168             }
1169             $return .= $setting->output_html($data);
1170         }
1171         $return .= '</fieldset>';
1172         return $return;
1173     }
1175     /**
1176      * Is this settigns page hidden in admin tree block?
1177      *
1178      * @return bool True if hidden
1179      */
1180     public function is_hidden() {
1181         return $this->hidden;
1182     }
1184     /**
1185      * Show we display Save button at the page bottom?
1186      * @return bool
1187      */
1188     public function show_save() {
1189         foreach($this->settings as $setting) {
1190             if (empty($setting->nosave)) {
1191                 return true;
1192             }
1193         }
1194         return false;
1195     }
1199 /**
1200  * Admin settings class. Only exists on setting pages.
1201  * Read & write happens at this level; no authentication.
1202  *
1203  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1204  */
1205 abstract class admin_setting {
1206 /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
1207     public $name;
1208     /** @var string localised name */
1209     public $visiblename;
1210     /** @var string localised long description */
1211     public $description;
1212     /** @var mixed Can be string or array of string */
1213     public $defaultsetting;
1214     /** @var string */
1215     public $updatedcallback;
1216     /** @var mixed can be String or Null.  Null means main config table */
1217     public $plugin; // null means main config table
1218     /** @var bool true indicates this setting does not actually save anything, just information */
1219     public $nosave = false;
1221     /**
1222      * Constructor
1223      * @param string $name unique ascii name, either 'mysetting' for settings that in config,
1224      *                     or 'myplugin/mysetting' for ones in config_plugins.
1225      * @param string $visiblename localised name
1226      * @param string $description localised long description
1227      * @param mixed $defaultsetting string or array depending on implementation
1228      */
1229     public function __construct($name, $visiblename, $description, $defaultsetting) {
1230         $this->parse_setting_name($name);
1231         $this->visiblename    = $visiblename;
1232         $this->description    = $description;
1233         $this->defaultsetting = $defaultsetting;
1234     }
1236     /**
1237      * Set up $this->name and potentially $this->plugin
1238      *
1239      * Set up $this->name and possibly $this->plugin based on whether $name looks
1240      * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1241      * on the names, that is, output a developer debug warning if the name
1242      * contains anything other than [a-zA-Z0-9_]+.
1243      *
1244      * @param string $name the setting name passed in to the constructor.
1245      */
1246     private function parse_setting_name($name) {
1247         $bits = explode('/', $name);
1248         if (count($bits) > 2) {
1249             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1250         }
1251         $this->name = array_pop($bits);
1252         if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1253             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1254         }
1255         if (!empty($bits)) {
1256             $this->plugin = array_pop($bits);
1257             if ($this->plugin === 'moodle') {
1258                 $this->plugin = null;
1259             } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1260                     throw new moodle_exception('invalidadminsettingname', '', '', $name);
1261                 }
1262         }
1263     }
1265     /**
1266      * Returns the fullname prefixed by the plugin
1267      * @return string
1268      */
1269     public function get_full_name() {
1270         return 's_'.$this->plugin.'_'.$this->name;
1271     }
1273     /**
1274      * Returns the ID string based on plugin and name
1275      * @return string
1276      */
1277     public function get_id() {
1278         return 'id_s_'.$this->plugin.'_'.$this->name;
1279     }
1281     /**
1282      * Returns the config if possible
1283      *
1284      * @return mixed returns config if successfull else null
1285      */
1286     public function config_read($name) {
1287         global $CFG;
1288         if (!empty($this->plugin)) {
1289             $value = get_config($this->plugin, $name);
1290             return $value === false ? NULL : $value;
1292         } else {
1293             if (isset($CFG->$name)) {
1294                 return $CFG->$name;
1295             } else {
1296                 return NULL;
1297             }
1298         }
1299     }
1301     /**
1302      * Used to set a config pair and log change
1303      *
1304      * @param string $name
1305      * @param mixed $value Gets converted to string if not null
1306      * @return bool Write setting to confix table
1307      */
1308     public function config_write($name, $value) {
1309         global $DB, $USER, $CFG;
1311         if ($this->nosave) {
1312             return true;
1313         }
1315         // make sure it is a real change
1316         $oldvalue = get_config($this->plugin, $name);
1317         $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1318         $value = is_null($value) ? null : (string)$value;
1320         if ($oldvalue === $value) {
1321             return true;
1322         }
1324         // store change
1325         set_config($name, $value, $this->plugin);
1327         // log change
1328         $log = new object();
1329         $log->userid       = during_initial_install() ? 0 :$USER->id; // 0 as user id during install
1330         $log->timemodified = time();
1331         $log->plugin       = $this->plugin;
1332         $log->name         = $name;
1333         $log->value        = $value;
1334         $log->oldvalue     = $oldvalue;
1335         $DB->insert_record('config_log', $log);
1337         return true; // BC only
1338     }
1340     /**
1341      * Returns current value of this setting
1342      * @return mixed array or string depending on instance, NULL means not set yet
1343      */
1344     public abstract function get_setting();
1346     /**
1347      * Returns default setting if exists
1348      * @return mixed array or string depending on instance; NULL means no default, user must supply
1349      */
1350     public function get_defaultsetting() {
1351         $adminroot =  admin_get_root(false, false);
1352         if (!empty($adminroot->custom_defaults)) {
1353             $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1354             if (isset($adminroot->custom_defaults[$plugin])) {
1355                 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid valie here ;-)
1356                     return $adminroot->custom_defaults[$plugin][$this->name];
1357                 }
1358             }
1359         }
1360         return $this->defaultsetting;
1361     }
1363     /**
1364      * Store new setting
1365      *
1366      * @param mixed $data string or array, must not be NULL
1367      * @return string empty string if ok, string error message otherwise
1368      */
1369     public abstract function write_setting($data);
1371     /**
1372      * Return part of form with setting
1373      * This function should always be overwritten
1374      *
1375      * @param mixed $data array or string depending on setting
1376      * @param string $query
1377      * @return string
1378      */
1379     public function output_html($data, $query='') {
1380     // should be overridden
1381         return;
1382     }
1384     /**
1385      * Function called if setting updated - cleanup, cache reset, etc.
1386      * @param string $functionname Sets the function name
1387      */
1388     public function set_updatedcallback($functionname) {
1389         $this->updatedcallback = $functionname;
1390     }
1392     /**
1393      * Is setting related to query text - used when searching
1394      * @param string $query
1395      * @return bool
1396      */
1397     public function is_related($query) {
1398         if (strpos(strtolower($this->name), $query) !== false) {
1399             return true;
1400         }
1401         $textlib = textlib_get_instance();
1402         if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1403             return true;
1404         }
1405         if (strpos($textlib->strtolower($this->description), $query) !== false) {
1406             return true;
1407         }
1408         $current = $this->get_setting();
1409         if (!is_null($current)) {
1410             if (is_string($current)) {
1411                 if (strpos($textlib->strtolower($current), $query) !== false) {
1412                     return true;
1413                 }
1414             }
1415         }
1416         $default = $this->get_defaultsetting();
1417         if (!is_null($default)) {
1418             if (is_string($default)) {
1419                 if (strpos($textlib->strtolower($default), $query) !== false) {
1420                     return true;
1421                 }
1422             }
1423         }
1424         return false;
1425     }
1428 /**
1429  * No setting - just heading and text.
1430  *
1431  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1432  */
1433 class admin_setting_heading extends admin_setting {
1434 /**
1435  * not a setting, just text
1436  * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1437  * @param string $heading heading
1438  * @param string $information text in box
1439  */
1440     public function __construct($name, $heading, $information) {
1441         $this->nosave = true;
1442         parent::__construct($name, $heading, $information, '');
1443     }
1445     /**
1446      * Always returns true
1447      * @return bool Always returns true
1448      */
1449     public function get_setting() {
1450         return true;
1451     }
1453     /**
1454      * Always returns true
1455      * @return bool Always returns true
1456      */
1457     public function get_defaultsetting() {
1458         return true;
1459     }
1461     /**
1462      * Never write settings
1463      * @return string Always returns an empty string
1464      */
1465     public function write_setting($data) {
1466     // do not write any setting
1467         return '';
1468     }
1470     /**
1471      * Returns an HTML string
1472      * @return string Returns an HTML string
1473      */
1474     public function output_html($data, $query='') {
1475         global $OUTPUT;
1476         $return = '';
1477         if ($this->visiblename != '') {
1478             $return .= $OUTPUT->heading('<a name="'.$this->name.'">'.highlightfast($query, $this->visiblename).'</a>', 3, 'main', true);
1479         }
1480         if ($this->description != '') {
1481             $return .= $OUTPUT->box(highlight($query, $this->description), 'generalbox formsettingheading');
1482         }
1483         return $return;
1484     }
1487 /**
1488  * The most flexibly setting, user is typing text
1489  *
1490  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1491  */
1492 class admin_setting_configtext extends admin_setting {
1494 /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
1495     public $paramtype;
1496     /** @var int default field size */
1497     public $size;
1499     /**
1500      * Config text contructor
1501      *
1502      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1503      * @param string $visiblename localised
1504      * @param string $description long localised info
1505      * @param string $defaultsetting
1506      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
1507      * @param int $size default field size
1508      */
1509     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
1510         $this->paramtype = $paramtype;
1511         if (!is_null($size)) {
1512             $this->size  = $size;
1513         } else {
1514             $this->size  = ($paramtype == PARAM_INT) ? 5 : 30;
1515         }
1516         parent::__construct($name, $visiblename, $description, $defaultsetting);
1517     }
1519     /**
1520      * Return the setting
1521      *
1522      * @return mixed returns config if successfull else null
1523      */
1524     public function get_setting() {
1525         return $this->config_read($this->name);
1526     }
1528     public function write_setting($data) {
1529         if ($this->paramtype === PARAM_INT and $data === '') {
1530         // do not complain if '' used instead of 0
1531             $data = 0;
1532         }
1533         // $data is a string
1534         $validated = $this->validate($data);
1535         if ($validated !== true) {
1536             return $validated;
1537         }
1538         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1539     }
1541     /**
1542      * Validate data before storage
1543      * @param string data
1544      * @return mixed true if ok string if error found
1545      */
1546     public function validate($data) {
1547         // allow paramtype to be a custom regex if it is the form of /pattern/
1548         if (preg_match('#^/.*/$#', $this->paramtype)) {
1549             if (preg_match($this->paramtype, $data)) {
1550                 return true;
1551             } else {
1552                 return get_string('validateerror', 'admin');
1553             }
1555         } else if ($this->paramtype === PARAM_RAW) {
1556             return true;
1558         } else {
1559             $cleaned = clean_param($data, $this->paramtype);
1560             if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
1561                 return true;
1562             } else {
1563                 return get_string('validateerror', 'admin');
1564             }
1565         }
1566     }
1568     /**
1569      * Return an XHTML string for the setting
1570      * @return string Returns an XHTML string
1571      */
1572     public function output_html($data, $query='') {
1573         $default = $this->get_defaultsetting();
1575         return format_admin_setting($this, $this->visiblename,
1576         '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
1577         $this->description, true, '', $default, $query);
1578     }
1581 /**
1582  * General text area without html editor.
1583  *
1584  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1585  */
1586 class admin_setting_configtextarea extends admin_setting_configtext {
1587     private $rows;
1588     private $cols;
1590     /**
1591      * @param string $name
1592      * @param string $visiblename
1593      * @param string $description
1594      * @param mixed $defaultsetting string or array
1595      * @param mixed $paramtype
1596      * @param string $cols The number of columns to make the editor
1597      * @param string $rows The number of rows to make the editor
1598      */
1599     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1600         $this->rows = $rows;
1601         $this->cols = $cols;
1602         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1603     }
1604     /**
1605      * Returns an XHTML string for the editor
1606      *
1607      * @param string $data
1608      * @param string $query
1609      * @return string XHTML string for the editor
1610      */
1611     public function output_html($data, $query='') {
1612         $default = $this->get_defaultsetting();
1614         $defaultinfo = $default;
1615         if (!is_null($default) and $default !== '') {
1616             $defaultinfo = "\n".$default;
1617         }
1619         return format_admin_setting($this, $this->visiblename,
1620         '<div class="form-textarea" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1621         $this->description, true, '', $defaultinfo, $query);
1622     }
1625 /**
1626  * General text area with html editor.
1627  */
1628 class admin_setting_confightmleditor extends admin_setting_configtext {
1629     private $rows;
1630     private $cols;
1632     /**
1633      * @param string $name
1634      * @param string $visiblename
1635      * @param string $description
1636      * @param mixed $defaultsetting string or array
1637      * @param mixed $paramtype
1638      */
1639     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1640         $this->rows = $rows;
1641         $this->cols = $cols;
1642         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1643         editors_head_setup();
1644     }
1645     /**
1646      * Returns an XHTML string for the editor
1647      *
1648      * @param string $data
1649      * @param string $query
1650      * @return string XHTML string for the editor
1651      */
1652     public function output_html($data, $query='') {
1653         $default = $this->get_defaultsetting();
1655         $defaultinfo = $default;
1656         if (!is_null($default) and $default !== '') {
1657             $defaultinfo = "\n".$default;
1658         }
1660         $editor = get_preferred_texteditor(FORMAT_HTML);
1661         $editor->use_editor($this->get_id(), array('noclean'=>true));
1663         return format_admin_setting($this, $this->visiblename,
1664         '<div class="form-textarea"><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1665         $this->description, true, '', $defaultinfo, $query);
1666     }
1669 /**
1670  * Password field, allows unmasking of password
1671  *
1672  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1673  */
1674 class admin_setting_configpasswordunmask extends admin_setting_configtext {
1675 /**
1676  * Constructor
1677  * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1678  * @param string $visiblename localised
1679  * @param string $description long localised info
1680  * @param string $defaultsetting default password
1681  */
1682     public function __construct($name, $visiblename, $description, $defaultsetting) {
1683         parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
1684     }
1686     /**
1687      * Returns XHTML for the field
1688      * Writes Javascript into the HTML below right before the last div
1689      *
1690      * @todo Make javascript available through newer methods if possible
1691      * @param string $data Value for the field
1692      * @param string $query Passed as final argument for format_admin_setting
1693      * @return string XHTML field
1694      */
1695     public function output_html($data, $query='') {
1696         $id = $this->get_id();
1697         $unmask = get_string('unmaskpassword', 'form');
1698         $unmaskjs = '<script type="text/javascript">
1699 //<![CDATA[
1700 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
1702 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
1704 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
1706 var unmaskchb = document.createElement("input");
1707 unmaskchb.setAttribute("type", "checkbox");
1708 unmaskchb.setAttribute("id", "'.$id.'unmask");
1709 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
1710 unmaskdiv.appendChild(unmaskchb);
1712 var unmasklbl = document.createElement("label");
1713 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
1714 if (is_ie) {
1715   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
1716 } else {
1717   unmasklbl.setAttribute("for", "'.$id.'unmask");
1719 unmaskdiv.appendChild(unmasklbl);
1721 if (is_ie) {
1722   // ugly hack to work around the famous onchange IE bug
1723   unmaskchb.onclick = function() {this.blur();};
1724   unmaskdiv.onclick = function() {this.blur();};
1726 //]]>
1727 </script>';
1728         return format_admin_setting($this, $this->visiblename,
1729         '<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>',
1730         $this->description, true, '', NULL, $query);
1731     }
1734 /**
1735  * Path to directory
1736  *
1737  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1738  */
1739 class admin_setting_configfile extends admin_setting_configtext {
1740 /**
1741  * Constructor
1742  * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1743  * @param string $visiblename localised
1744  * @param string $description long localised info
1745  * @param string $defaultdirectory default directory location
1746  */
1747     public function __construct($name, $visiblename, $description, $defaultdirectory) {
1748         parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
1749     }
1751     /**
1752      * Returns XHTML for the field
1753      *
1754      * Returns XHTML for the field and also checks whether the file
1755      * specified in $data exists using file_exists()
1756      *
1757      * @param string $data File name and path to use in value attr
1758      * @param string $query
1759      * @return string XHTML field
1760      */
1761     public function output_html($data, $query='') {
1762         $default = $this->get_defaultsetting();
1764         if ($data) {
1765             if (file_exists($data)) {
1766                 $executable = '<span class="pathok">&#x2714;</span>';
1767             } else {
1768                 $executable = '<span class="patherror">&#x2718;</span>';
1769             }
1770         } else {
1771             $executable = '';
1772         }
1774         return format_admin_setting($this, $this->visiblename,
1775         '<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>',
1776         $this->description, true, '', $default, $query);
1777     }
1780 /**
1781  * Path to executable file
1782  *
1783  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1784  */
1785 class admin_setting_configexecutable extends admin_setting_configfile {
1787 /**
1788  * Returns an XHTML field
1789  *
1790  * @param string $data This is the value for the field
1791  * @param string $query
1792  * @return string XHTML field
1793  */
1794     public function output_html($data, $query='') {
1795         $default = $this->get_defaultsetting();
1797         if ($data) {
1798             if (file_exists($data) and is_executable($data)) {
1799                 $executable = '<span class="pathok">&#x2714;</span>';
1800             } else {
1801                 $executable = '<span class="patherror">&#x2718;</span>';
1802             }
1803         } else {
1804             $executable = '';
1805         }
1807         return format_admin_setting($this, $this->visiblename,
1808         '<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>',
1809         $this->description, true, '', $default, $query);
1810     }
1813 /**
1814  * Path to directory
1815  *
1816  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1817  */
1818 class admin_setting_configdirectory extends admin_setting_configfile {
1820 /**
1821  * Returns an XHTML field
1822  *
1823  * @param string $data This is the value for the field
1824  * @param string $query
1825  * @return string XHTML
1826  */
1827     public function output_html($data, $query='') {
1828         $default = $this->get_defaultsetting();
1830         if ($data) {
1831             if (file_exists($data) and is_dir($data)) {
1832                 $executable = '<span class="pathok">&#x2714;</span>';
1833             } else {
1834                 $executable = '<span class="patherror">&#x2718;</span>';
1835             }
1836         } else {
1837             $executable = '';
1838         }
1840         return format_admin_setting($this, $this->visiblename,
1841         '<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>',
1842         $this->description, true, '', $default, $query);
1843     }
1846 /**
1847  * Checkbox
1848  *
1849  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1850  */
1851 class admin_setting_configcheckbox extends admin_setting {
1852 /** @var string Value used when checked */
1853     public $yes;
1854     /** @var string Value used when not checked */
1855     public $no;
1857     /**
1858      * Constructor
1859      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1860      * @param string $visiblename localised
1861      * @param string $description long localised info
1862      * @param string $defaultsetting
1863      * @param string $yes value used when checked
1864      * @param string $no value used when not checked
1865      */
1866     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
1867         parent::__construct($name, $visiblename, $description, $defaultsetting);
1868         $this->yes = (string)$yes;
1869         $this->no  = (string)$no;
1870     }
1872     /**
1873      * Retrieves the current setting using the objects name
1874      *
1875      * @return string
1876      */
1877     public function get_setting() {
1878         return $this->config_read($this->name);
1879     }
1881     /**
1882      * Sets the value for the setting
1883      *
1884      * Sets the value for the setting to either the yes or no values
1885      * of the object by comparing $data to yes
1886      *
1887      * @param mixed $data Gets converted to str for comparison against yes value
1888      * @return string empty string or error
1889      */
1890     public function write_setting($data) {
1891         if ((string)$data === $this->yes) { // convert to strings before comparison
1892             $data = $this->yes;
1893         } else {
1894             $data = $this->no;
1895         }
1896         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1897     }
1899     /**
1900      * Returns an XHTML checkbox field
1901      *
1902      * @param string $data If $data matches yes then checkbox is checked
1903      * @param string $query
1904      * @return string XHTML field
1905      */
1906     public function output_html($data, $query='') {
1907         $default = $this->get_defaultsetting();
1909         if (!is_null($default)) {
1910             if ((string)$default === $this->yes) {
1911                 $defaultinfo = get_string('checkboxyes', 'admin');
1912             } else {
1913                 $defaultinfo = get_string('checkboxno', 'admin');
1914             }
1915         } else {
1916             $defaultinfo = NULL;
1917         }
1919         if ((string)$data === $this->yes) { // convert to strings before comparison
1920             $checked = 'checked="checked"';
1921         } else {
1922             $checked = '';
1923         }
1925         return format_admin_setting($this, $this->visiblename,
1926         '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
1927             .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
1928         $this->description, true, '', $defaultinfo, $query);
1929     }
1932 /**
1933  * Multiple checkboxes, each represents different value, stored in csv format
1934  *
1935  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1936  */
1937 class admin_setting_configmulticheckbox extends admin_setting {
1938 /** @var array Array of choices value=>label */
1939     public $choices;
1941     /**
1942      * Constructor: uses parent::__construct
1943      *
1944      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1945      * @param string $visiblename localised
1946      * @param string $description long localised info
1947      * @param array $defaultsetting array of selected
1948      * @param array $choices array of $value=>$label for each checkbox
1949      */
1950     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
1951         $this->choices = $choices;
1952         parent::__construct($name, $visiblename, $description, $defaultsetting);
1953     }
1955     /**
1956      * This public function may be used in ancestors for lazy loading of choices
1957      *
1958      * @todo Check if this function is still required content commented out only returns true
1959      * @return bool true if loaded, false if error
1960      */
1961     public function load_choices() {
1962         /*
1963         if (is_array($this->choices)) {
1964             return true;
1965         }
1966         .... load choices here
1967         */
1968         return true;
1969     }
1971     /**
1972      * Is setting related to query text - used when searching
1973      *
1974      * @param string $query
1975      * @return bool true on related, false on not or failure
1976      */
1977     public function is_related($query) {
1978         if (!$this->load_choices() or empty($this->choices)) {
1979             return false;
1980         }
1981         if (parent::is_related($query)) {
1982             return true;
1983         }
1985         $textlib = textlib_get_instance();
1986         foreach ($this->choices as $desc) {
1987             if (strpos($textlib->strtolower($desc), $query) !== false) {
1988                 return true;
1989             }
1990         }
1991         return false;
1992     }
1994     /**
1995      * Returns the current setting if it is set
1996      *
1997      * @return mixed null if null, else an array
1998      */
1999     public function get_setting() {
2000         $result = $this->config_read($this->name);
2002         if (is_null($result)) {
2003             return NULL;
2004         }
2005         if ($result === '') {
2006             return array();
2007         }
2008         $enabled = explode(',', $result);
2009         $setting = array();
2010         foreach ($enabled as $option) {
2011             $setting[$option] = 1;
2012         }
2013         return $setting;
2014     }
2016     /**
2017      * Saves the setting(s) provided in $data
2018      *
2019      * @param array $data An array of data, if not array returns empty str
2020      * @return mixed empty string on useless data or bool true=success, false=failed
2021      */
2022     public function write_setting($data) {
2023         if (!is_array($data)) {
2024             return ''; // ignore it
2025         }
2026         if (!$this->load_choices() or empty($this->choices)) {
2027             return '';
2028         }
2029         unset($data['xxxxx']);
2030         $result = array();
2031         foreach ($data as $key => $value) {
2032             if ($value and array_key_exists($key, $this->choices)) {
2033                 $result[] = $key;
2034             }
2035         }
2036         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2037     }
2039     /**
2040      * Returns XHTML field(s) as required by choices
2041      *
2042      * Relies on data being an array should data ever be another valid vartype with
2043      * acceptable value this may cause a warning/error
2044      * if (!is_array($data)) would fix the problem
2045      *
2046      * @todo Add vartype handling to ensure $data is an array
2047      *
2048      * @param array $data An array of checked values
2049      * @param string $query
2050      * @return string XHTML field
2051      */
2052     public function output_html($data, $query='') {
2053         if (!$this->load_choices() or empty($this->choices)) {
2054             return '';
2055         }
2056         $default = $this->get_defaultsetting();
2057         if (is_null($default)) {
2058             $default = array();
2059         }
2060         if (is_null($data)) {
2061             $data = array();
2062         }
2063         $options = array();
2064         $defaults = array();
2065         foreach ($this->choices as $key=>$description) {
2066             if (!empty($data[$key])) {
2067                 $checked = 'checked="checked"';
2068             } else {
2069                 $checked = '';
2070             }
2071             if (!empty($default[$key])) {
2072                 $defaults[] = $description;
2073             }
2075             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
2076                 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
2077         }
2079         if (is_null($default)) {
2080             $defaultinfo = NULL;
2081         } else if (!empty($defaults)) {
2082                 $defaultinfo = implode(', ', $defaults);
2083             } else {
2084                 $defaultinfo = get_string('none');
2085             }
2087         $return = '<div class="form-multicheckbox">';
2088         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2089         if ($options) {
2090             $return .= '<ul>';
2091             foreach ($options as $option) {
2092                 $return .= '<li>'.$option.'</li>';
2093             }
2094             $return .= '</ul>';
2095         }
2096         $return .= '</div>';
2098         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2100     }
2103 /**
2104  * Multiple checkboxes 2, value stored as string 00101011
2105  *
2106  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2107  */
2108 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
2110 /**
2111  * Returns the setting if set
2112  *
2113  * @return mixed null if not set, else an array of set settings
2114  */
2115     public function get_setting() {
2116         $result = $this->config_read($this->name);
2117         if (is_null($result)) {
2118             return NULL;
2119         }
2120         if (!$this->load_choices()) {
2121             return NULL;
2122         }
2123         $result = str_pad($result, count($this->choices), '0');
2124         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2125         $setting = array();
2126         foreach ($this->choices as $key=>$unused) {
2127             $value = array_shift($result);
2128             if ($value) {
2129                 $setting[$key] = 1;
2130             }
2131         }
2132         return $setting;
2133     }
2135     /**
2136      * Save setting(s) provided in $data param
2137      *
2138      * @param array $data An array of settings to save
2139      * @return mixed empty string for bad data or bool true=>success, false=>error
2140      */
2141     public function write_setting($data) {
2142         if (!is_array($data)) {
2143             return ''; // ignore it
2144         }
2145         if (!$this->load_choices() or empty($this->choices)) {
2146             return '';
2147         }
2148         $result = '';
2149         foreach ($this->choices as $key=>$unused) {
2150             if (!empty($data[$key])) {
2151                 $result .= '1';
2152             } else {
2153                 $result .= '0';
2154             }
2155         }
2156         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2157     }
2160 /**
2161  * Select one value from list
2162  *
2163  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2164  */
2165 class admin_setting_configselect extends admin_setting {
2166 /** @var array Array of choices value=>label */
2167     public $choices;
2169     /**
2170      * Constructor
2171      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2172      * @param string $visiblename localised
2173      * @param string $description long localised info
2174      * @param string $defaultsetting
2175      * @param array $choices array of $value=>$label for each selection
2176      */
2177     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2178         $this->choices = $choices;
2179         parent::__construct($name, $visiblename, $description, $defaultsetting);
2180     }
2182     /**
2183      * This function may be used in ancestors for lazy loading of choices
2184      *
2185      * Override this method if loading of choices is expensive, such
2186      * as when it requires multiple db requests.
2187      *
2188      * @return bool true if loaded, false if error
2189      */
2190     public function load_choices() {
2191         /*
2192         if (is_array($this->choices)) {
2193             return true;
2194         }
2195         .... load choices here
2196         */
2197         return true;
2198     }
2200     /**
2201      * Check if this is $query is related to a choice
2202      *
2203      * @param string $query
2204      * @return bool true if related, false if not
2205      */
2206     public function is_related($query) {
2207         if (parent::is_related($query)) {
2208             return true;
2209         }
2210         if (!$this->load_choices()) {
2211             return false;
2212         }
2213         $textlib = textlib_get_instance();
2214         foreach ($this->choices as $key=>$value) {
2215             if (strpos($textlib->strtolower($key), $query) !== false) {
2216                 return true;
2217             }
2218             if (strpos($textlib->strtolower($value), $query) !== false) {
2219                 return true;
2220             }
2221         }
2222         return false;
2223     }
2225     /**
2226      * Return the setting
2227      *
2228      * @return mixed returns config if successfull else null
2229      */
2230     public function get_setting() {
2231         return $this->config_read($this->name);
2232     }
2234     /**
2235      * Save a setting
2236      *
2237      * @param string $data
2238      * @return string empty of error string
2239      */
2240     public function write_setting($data) {
2241         if (!$this->load_choices() or empty($this->choices)) {
2242             return '';
2243         }
2244         if (!array_key_exists($data, $this->choices)) {
2245             return ''; // ignore it
2246         }
2248         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2249     }
2251     /**
2252      * Returns XHTML select field
2253      *
2254      * Ensure the options are loaded, and generate the XHTML for the select
2255      * element and any warning message. Separating this out from output_html
2256      * makes it easier to subclass this class.
2257      *
2258      * @param string $data the option to show as selected.
2259      * @param string $current the currently selected option in the database, null if none.
2260      * @param string $default the default selected option.
2261      * @return array the HTML for the select element, and a warning message.
2262      */
2263     public function output_select_html($data, $current, $default, $extraname = '') {
2264         if (!$this->load_choices() or empty($this->choices)) {
2265             return array('', '');
2266         }
2268         $warning = '';
2269         if (is_null($current)) {
2270         // first run
2271         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2272             // no warning
2273             } else if (!array_key_exists($current, $this->choices)) {
2274                     $warning = get_string('warningcurrentsetting', 'admin', s($current));
2275                     if (!is_null($default) and $data == $current) {
2276                         $data = $default; // use default instead of first value when showing the form
2277                     }
2278                 }
2280         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
2281         foreach ($this->choices as $key => $value) {
2282         // the string cast is needed because key may be integer - 0 is equal to most strings!
2283             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
2284         }
2285         $selecthtml .= '</select>';
2286         return array($selecthtml, $warning);
2287     }
2289     /**
2290      * Returns XHTML select field and wrapping div(s)
2291      *
2292      * @see output_select_html()
2293      *
2294      * @param string $data the option to show as selected
2295      * @param string $query
2296      * @return string XHTML field and wrapping div
2297      */
2298     public function output_html($data, $query='') {
2299         $default = $this->get_defaultsetting();
2300         $current = $this->get_setting();
2302         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2303         if (!$selecthtml) {
2304             return '';
2305         }
2307         if (!is_null($default) and array_key_exists($default, $this->choices)) {
2308             $defaultinfo = $this->choices[$default];
2309         } else {
2310             $defaultinfo = NULL;
2311         }
2313         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
2315         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
2316     }
2319 /**
2320  * Select multiple items from list
2321  *
2322  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2323  */
2324 class admin_setting_configmultiselect extends admin_setting_configselect {
2325 /**
2326  * Constructor
2327  * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2328  * @param string $visiblename localised
2329  * @param string $description long localised info
2330  * @param array $defaultsetting array of selected items
2331  * @param array $choices array of $value=>$label for each list item
2332  */
2333     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2334         parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
2335     }
2337     /**
2338      * Returns the select setting(s)
2339      *
2340      * @return mixed null or array. Null if no settings else array of setting(s)
2341      */
2342     public function get_setting() {
2343         $result = $this->config_read($this->name);
2344         if (is_null($result)) {
2345             return NULL;
2346         }
2347         if ($result === '') {
2348             return array();
2349         }
2350         return explode(',', $result);
2351     }
2353     /**
2354      * Saves setting(s) provided through $data
2355      *
2356      * Potential bug in the works should anyone call with this function
2357      * using a vartype that is not an array
2358      *
2359      * @todo Add vartype handling to ensure $data is an array
2360      * @param array $data
2361      */
2362     public function write_setting($data) {
2363         if (!is_array($data)) {
2364             return ''; //ignore it
2365         }
2366         if (!$this->load_choices() or empty($this->choices)) {
2367             return '';
2368         }
2370         unset($data['xxxxx']);
2372         $save = array();
2373         foreach ($data as $value) {
2374             if (!array_key_exists($value, $this->choices)) {
2375                 continue; // ignore it
2376             }
2377             $save[] = $value;
2378         }
2380         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2381     }
2383     /**
2384      * Is setting related to query text - used when searching
2385      *
2386      * @param string $query
2387      * @return bool true if related, false if not
2388      */
2389     public function is_related($query) {
2390         if (!$this->load_choices() or empty($this->choices)) {
2391             return false;
2392         }
2393         if (parent::is_related($query)) {
2394             return true;
2395         }
2397         $textlib = textlib_get_instance();
2398         foreach ($this->choices as $desc) {
2399             if (strpos($textlib->strtolower($desc), $query) !== false) {
2400                 return true;
2401             }
2402         }
2403         return false;
2404     }
2406     /**
2407      * Returns XHTML multi-select field
2408      *
2409      * @todo Add vartype handling to ensure $data is an array
2410      * @param array $data Array of values to select by default
2411      * @param string $query
2412      * @return string XHTML multi-select field
2413      */
2414     public function output_html($data, $query='') {
2415         if (!$this->load_choices() or empty($this->choices)) {
2416             return '';
2417         }
2418         $choices = $this->choices;
2419         $default = $this->get_defaultsetting();
2420         if (is_null($default)) {
2421             $default = array();
2422         }
2423         if (is_null($data)) {
2424             $data = array();
2425         }
2427         $defaults = array();
2428         $size = min(10, count($this->choices));
2429         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2430         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
2431         foreach ($this->choices as $key => $description) {
2432             if (in_array($key, $data)) {
2433                 $selected = 'selected="selected"';
2434             } else {
2435                 $selected = '';
2436             }
2437             if (in_array($key, $default)) {
2438                 $defaults[] = $description;
2439             }
2441             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2442         }
2444         if (is_null($default)) {
2445             $defaultinfo = NULL;
2446         } if (!empty($defaults)) {
2447             $defaultinfo = implode(', ', $defaults);
2448         } else {
2449             $defaultinfo = get_string('none');
2450         }
2452         $return .= '</select></div>';
2453         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
2454     }
2457 /**
2458  * Time selector
2459  *
2460  * This is a liiitle bit messy. we're using two selects, but we're returning
2461  * them as an array named after $name (so we only use $name2 internally for the setting)
2462  *
2463  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2464  */
2465 class admin_setting_configtime extends admin_setting {
2466 /** @var string Used for setting second select (minutes) */
2467     public $name2;
2469     /**
2470      * Constructor
2471      * @param string $hoursname setting for hours
2472      * @param string $minutesname setting for hours
2473      * @param string $visiblename localised
2474      * @param string $description long localised info
2475      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2476      */
2477     public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
2478         $this->name2 = $minutesname;
2479         parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
2480     }
2482     /**
2483      * Get the selected time
2484      *
2485      * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
2486      */
2487     public function get_setting() {
2488         $result1 = $this->config_read($this->name);
2489         $result2 = $this->config_read($this->name2);
2490         if (is_null($result1) or is_null($result2)) {
2491             return NULL;
2492         }
2494         return array('h' => $result1, 'm' => $result2);
2495     }
2497     /**
2498      * Store the time (hours and minutes)
2499      *
2500      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2501      * @return bool true if success, false if not
2502      */
2503     public function write_setting($data) {
2504         if (!is_array($data)) {
2505             return '';
2506         }
2508         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2509         return ($result ? '' : get_string('errorsetting', 'admin'));
2510     }
2512     /**
2513      * Returns XHTML time select fields
2514      *
2515      * @param array $data Must be form 'h'=>xx, 'm'=>xx
2516      * @param string $query
2517      * @return string XHTML time select fields and wrapping div(s)
2518      */
2519     public function output_html($data, $query='') {
2520         $default = $this->get_defaultsetting();
2522         if (is_array($default)) {
2523             $defaultinfo = $default['h'].':'.$default['m'];
2524         } else {
2525             $defaultinfo = NULL;
2526         }
2528         $return = '<div class="form-time defaultsnext">'.
2529             '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
2530         for ($i = 0; $i < 24; $i++) {
2531             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2532         }
2533         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2534         for ($i = 0; $i < 60; $i += 5) {
2535             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2536         }
2537         $return .= '</select></div>';
2538         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2539     }
2543 /**
2544  * Used to validate a textarea used for ip addresses
2545  *
2546  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2547  */
2548 class admin_setting_configiplist extends admin_setting_configtextarea {
2550 /**
2551  * Validate the contents of the textarea as IP addresses
2552  *
2553  * Used to validate a new line seperated list of IP addresses collected from
2554  * a textarea control
2555  *
2556  * @param string $data A list of IP Addresses seperated by new lines
2557  * @return mixed bool true for success or string:error on failure
2558  */
2559     public function validate($data) {
2560         if(!empty($data)) {
2561             $ips = explode("\n", $data);
2562         } else {
2563             return true;
2564         }
2565         $result = true;
2566         foreach($ips as $ip) {
2567             $ip = trim($ip);
2568             if(preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
2569                 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
2570                 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
2571                 $result = true;
2572             } else {
2573                 $result = false;
2574                 break;
2575             }
2576         }
2577         if($result) {
2578             return true;
2579         } else {
2580             return get_string('validateerror', 'admin');
2581         }
2582     }
2585 /**
2586  * An admin setting for selecting one or more users who have a capability
2587  * in the system context
2588  *
2589  * An admin setting for selecting one or more users, who have a particular capability
2590  * in the system context. Warning, make sure the list will never be too long. There is
2591  * no paging or searching of this list.
2592  *
2593  * To correctly get a list of users from this config setting, you need to call the
2594  * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
2595  *
2596  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2597  */
2598 class admin_setting_users_with_capability extends admin_setting_configmultiselect {
2599 /** @var string The capabilities name */
2600     protected $capability;
2602     /**
2603      * Constructor.
2604      *
2605      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2606      * @param string $visiblename localised name
2607      * @param string $description localised long description
2608      * @param array $defaultsetting array of usernames
2609      * @param string $capability string capability name.
2610      */
2611     function __construct($name, $visiblename, $description, $defaultsetting, $capability) {
2612         $this->capability = $capability;
2613         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
2614     }
2616     /**
2617      * Load all of the uses who have the capability into choice array
2618      *
2619      * @return bool Always returns true
2620      */
2621     function load_choices() {
2622         if (is_array($this->choices)) {
2623             return true;
2624         }
2625         $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM),
2626             $this->capability, 'u.id,u.username,u.firstname,u.lastname', 'u.lastname,u.firstname');
2627         $this->choices = array(
2628             '$@NONE@$' => get_string('nobody'),
2629             '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
2630         );
2631         foreach ($users as $user) {
2632             $this->choices[$user->username] = fullname($user);
2633         }
2634         return true;
2635     }
2637     /**
2638      * Returns the default setting for class
2639      *
2640      * @return mixed Array, or string. Empty string if no default
2641      */
2642     public function get_defaultsetting() {
2643         $this->load_choices();
2644         $defaultsetting = parent::get_defaultsetting();
2645         if (empty($defaultsetting)) {
2646             return array('$@NONE@$');
2647         } else if (array_key_exists($defaultsetting, $this->choices)) {
2648                 return $defaultsetting;
2649             } else {
2650                 return '';
2651             }
2652     }
2654     /**
2655      * Returns the current setting
2656      *
2657      * @return mixed array or string
2658      */
2659     public function get_setting() {
2660         $result = parent::get_setting();
2661         if (empty($result)) {
2662             $result = array('$@NONE@$');
2663         }
2664         return $result;
2665     }
2667     /**
2668      * Save the chosen setting provided as $data
2669      *
2670      * @param array $data
2671      * @return mixed string or array
2672      */
2673     public function write_setting($data) {
2674     // If all is selected, remove any explicit options.
2675         if (in_array('$@ALL@$', $data)) {
2676             $data = array('$@ALL@$');
2677         }
2678         // None never needs to be writted to the DB.
2679         if (in_array('$@NONE@$', $data)) {
2680             unset($data[array_search('$@NONE@$', $data)]);
2681         }
2682         return parent::write_setting($data);
2683     }
2686 /**
2687  * Special checkbox for calendar - resets SESSION vars.
2688  *
2689  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2690  */
2691 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
2692 /**
2693  * Calls the parent::__construct with default values
2694  *
2695  * name =>  calendar_adminseesall
2696  * visiblename => get_string('adminseesall', 'admin')
2697  * description => get_string('helpadminseesall', 'admin')
2698  * defaultsetting => 0
2699  */
2700     public function __construct() {
2701         parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
2702             get_string('helpadminseesall', 'admin'), '0');
2703     }
2705     /**
2706      * Stores the setting passed in $data
2707      *
2708      * @param mixed gets converted to string for comparison
2709      * @return string empty string or error message
2710      */
2711     public function write_setting($data) {
2712         global $SESSION;
2713         unset($SESSION->cal_courses_shown);
2714         return parent::write_setting($data);
2715     }
2718 /**
2719  * Special select for settings that are altered in setup.php and can not be altered on the fly
2720  *
2721  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2722  */
2723 class admin_setting_special_selectsetup extends admin_setting_configselect {
2724 /**
2725  * Reads the setting directly from the database
2726  *
2727  * @return mixed
2728  */
2729     public function get_setting() {
2730     // read directly from db!
2731         return get_config(NULL, $this->name);
2732     }
2734     /**
2735      * Save the setting passed in $data
2736      *
2737      * @param string $data The setting to save
2738      * @return string empty or error message
2739      */
2740     public function write_setting($data) {
2741         global $CFG;
2742         // do not change active CFG setting!
2743         $current = $CFG->{$this->name};
2744         $result = parent::write_setting($data);
2745         $CFG->{$this->name} = $current;
2746         return $result;
2747     }
2750 /**
2751  * Special select for frontpage - stores data in course table
2752  *
2753  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2754  */
2755 class admin_setting_sitesetselect extends admin_setting_configselect {
2756 /**
2757  * Returns the site name for the selected site
2758  *
2759  * @see get_site()
2760  * @return string The site name of the selected site
2761  */
2762     public function get_setting() {
2763         $site = get_site();
2764         return $site->{$this->name};
2765     }
2766     /**
2767      * Updates the database and save the setting
2768      *
2769      * @param string data
2770      * @return string empty or error message
2771      */
2772     public function write_setting($data) {
2773         global $DB, $SITE;
2774         if (!in_array($data, array_keys($this->choices))) {
2775             return get_string('errorsetting', 'admin');
2776         }
2777         $record = new stdClass();
2778         $record->id           = SITEID;
2779         $temp                 = $this->name;
2780         $record->$temp        = $data;
2781         $record->timemodified = time();
2782         // update $SITE
2783         $SITE->{$this->name} = $data;
2784         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
2785     }
2788 /**
2789  * Select for blog's bloglevel setting: if set to 0, will set blog_menu
2790  * block to hidden.
2791  *
2792  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2793  */
2794 class admin_setting_bloglevel extends admin_setting_configselect {
2795     /**
2796      * Updates the database and save the setting
2797      *
2798      * @param string data
2799      * @return string empty or error message
2800      */
2801     public function write_setting($data) {
2802         global $DB;
2803         if ($data['bloglevel'] == 0) {
2804             $DB->set_field('block', 'visible', 0, array('name' => 'blog_menu'));
2805         } else {
2806             $DB->set_field('block', 'visible', 1, array('name' => 'blog_menu'));
2807         }
2808         return parent::write_setting($data);
2809     }
2812 /**
2813  * Special select - lists on the frontpage - hacky
2814  *
2815  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2816  */
2817 class admin_setting_courselist_frontpage extends admin_setting {
2818 /** @var array Array of choices value=>label */
2819     public $choices;
2821     /**
2822      * Construct override, requires one param
2823      *
2824      * @param bool $loggedin Is the user logged in
2825      */
2826     public function __construct($loggedin) {
2827         global $CFG;
2828         require_once($CFG->dirroot.'/course/lib.php');
2829         $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
2830         $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
2831         $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
2832         $defaults    = array(FRONTPAGECOURSELIST);
2833         parent::__construct($name, $visiblename, $description, $defaults);
2834     }
2836     /**
2837      * Loads the choices available
2838      *
2839      * @return bool always returns true
2840      */
2841     public function load_choices() {
2842         global $DB;
2843         if (is_array($this->choices)) {
2844             return true;
2845         }
2846         $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
2847             FRONTPAGECOURSELIST    => get_string('frontpagecourselist'),
2848             FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
2849             FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
2850             'none'                 => get_string('none'));
2851         if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
2852             unset($this->choices[FRONTPAGECOURSELIST]);
2853         }
2854         return true;
2855     }
2856     /**
2857      * Returns the selected settings
2858      *
2859      * @param mixed array or setting or null
2860      */
2861     public function get_setting() {
2862         $result = $this->config_read($this->name);
2863         if (is_null($result)) {
2864             return NULL;
2865         }
2866         if ($result === '') {
2867             return array();
2868         }
2869         return explode(',', $result);
2870     }
2872     /**
2873      * Save the selected options
2874      *
2875      * @param array $data
2876      * @return mixed empty string (data is not an array) or bool true=success false=failure
2877      */
2878     public function write_setting($data) {
2879         if (!is_array($data)) {
2880             return '';
2881         }
2882         $this->load_choices();
2883         $save = array();
2884         foreach($data as $datum) {
2885             if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
2886                 continue;
2887             }
2888             $save[$datum] = $datum; // no duplicates
2889         }
2890         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2891     }
2893     /**
2894      * Return XHTML select field and wrapping div
2895      *
2896      * @todo Add vartype handling to make sure $data is an array
2897      * @param array $data Array of elements to select by default
2898      * @return string XHTML select field and wrapping div
2899      */
2900     public function output_html($data, $query='') {
2901         $this->load_choices();
2902         $currentsetting = array();
2903         foreach ($data as $key) {
2904             if ($key != 'none' and array_key_exists($key, $this->choices)) {
2905                 $currentsetting[] = $key; // already selected first
2906             }
2907         }
2909         $return = '<div class="form-group">';
2910         for ($i = 0; $i < count($this->choices) - 1; $i++) {
2911             if (!array_key_exists($i, $currentsetting)) {
2912                 $currentsetting[$i] = 'none'; //none
2913             }
2914             $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
2915             foreach ($this->choices as $key => $value) {
2916                 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
2917             }
2918             $return .= '</select>';
2919             if ($i !== count($this->choices) - 2) {
2920                 $return .= '<br />';
2921             }
2922         }
2923         $return .= '</div>';
2925         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
2926     }
2929 /**
2930  * Special checkbox for frontpage - stores data in course table
2931  *
2932  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2933  */
2934 class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
2935 /**
2936  * Returns the current sites name
2937  *
2938  * @return string
2939  */
2940     public function get_setting() {
2941         $site = get_site();
2942         return $site->{$this->name};
2943     }
2945     /**
2946      * Save the selected setting
2947      *
2948      * @param string $data The selected site
2949      * @return string empty string or error message
2950      */
2951     public function write_setting($data) {
2952         global $DB, $SITE;
2953         $record = new object();
2954         $record->id            = SITEID;
2955         $record->{$this->name} = ($data == '1' ? 1 : 0);
2956         $record->timemodified  = time();
2957         // update $SITE
2958         $SITE->{$this->name} = $data;
2959         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
2960     }
2963 /**
2964  * Special text for frontpage - stores data in course table.
2965  * Empty string means not set here. Manual setting is required.
2966  *
2967  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2968  */
2969 class admin_setting_sitesettext extends admin_setting_configtext {
2970 /**
2971  * Return the current setting
2972  *
2973  * @return mixed string or null
2974  */
2975     public function get_setting() {
2976         $site = get_site();
2977         return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
2978     }
2980     /**
2981      * Validate the selected data
2982      *
2983      * @param string $data The selected value to validate
2984      * @return mixed true or message string
2985      */
2986     public function validate($data) {
2987         $cleaned = clean_param($data, PARAM_MULTILANG);
2988         if ($cleaned === '') {
2989             return get_string('required');
2990         }
2991         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
2992             return true;
2993         } else {
2994             return get_string('validateerror', 'admin');
2995         }
2996     }
2998     /**
2999      * Save the selected setting
3000      *
3001      * @param string $data The selected value
3002      * @return string emtpy or error message
3003      */
3004     public function write_setting($data) {
3005         global $DB, $SITE;
3006         $data = trim($data);
3007         $validated = $this->validate($data);
3008         if ($validated !== true) {
3009             return $validated;
3010         }
3012         $record = new object();
3013         $record->id            = SITEID;
3014         $record->{$this->name} = $data;
3015         $record->timemodified  = time();
3016         // update $SITE
3017         $SITE->{$this->name} = $data;
3018         return ($DB->update_record('course', $record) ? '' : get_string('dbupdatefailed', 'error'));
3019     }
3022 /**
3023  * Special text editor for site description.
3024  *
3025  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3026  */
3027 class admin_setting_special_frontpagedesc extends admin_setting {
3028 /**
3029  * Calls parent::__construct with specific arguments
3030  */
3031     public function __construct() {
3032         parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
3033         editors_head_setup();
3034     }
3036     /**
3037      * Return the current setting
3038      * @return string The current setting
3039      */
3040     public function get_setting() {
3041         $site = get_site();
3042         return $site->{$this->name};
3043     }
3045     /**
3046      * Save the new setting
3047      *
3048      * @param string $data The new value to save
3049      * @return string empty or error message
3050      */
3051     public function write_setting($data) {
3052         global $DB, $SITE;
3053         $record = new object();
3054         $record->id            = SITEID;
3055         $record->{$this->name} = $data;
3056         $record->timemodified  = time();
3057         $SITE->{$this->name} = $data;
3058         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3059     }
3061     /**
3062      * Returns XHTML for the field plus wrapping div
3063      *
3064      * @param string $data The current value
3065      * @param string $query
3066      * @return string The XHTML output
3067      */
3068     public function output_html($data, $query='') {
3069         global $CFG;
3071         $CFG->adminusehtmleditor = can_use_html_editor();
3072         $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
3074         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3075     }
3078 /**
3079  * Special font selector for use in admin section
3080  *
3081  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3082  */
3083 class admin_setting_special_editorfontlist extends admin_setting {
3085 /**
3086  * Construct method, calls parent::__construct with specific args
3087  */
3088     public function __construct() {
3089         global $CFG;
3090         $name = 'editorfontlist';
3091         $visiblename = get_string('editorfontlist', 'admin');
3092         $description = get_string('configeditorfontlist', 'admin');
3093         $defaults = array('k0' => 'Trebuchet',
3094             'v0' => 'Trebuchet MS,Verdana,Arial,Helvetica,sans-serif',
3095             'k1' => 'Arial',
3096             'v1' => 'arial,helvetica,sans-serif',
3097             'k2' => 'Courier New',
3098             'v2' => 'courier new,courier,monospace',
3099             'k3' => 'Georgia',
3100             'v3' => 'georgia,times new roman,times,serif',
3101             'k4' => 'Tahoma',
3102             'v4' => 'tahoma,arial,helvetica,sans-serif',
3103             'k5' => 'Times New Roman',
3104             'v5' => 'times new roman,times,serif',
3105             'k6' => 'Verdana',
3106             'v6' => 'verdana,arial,helvetica,sans-serif',
3107             'k7' => 'Impact',
3108             'v7' => 'impact',
3109             'k8' => 'Wingdings',
3110             'v8' => 'wingdings');
3111         parent::__construct($name, $visiblename, $description, $defaults);
3112     }
3114     /**
3115      * Return the current setting
3116      *
3117      * @return array Array of the current setting(s)
3118      */
3119     public function get_setting() {
3120         global $CFG;
3121         $result = $this->config_read($this->name);
3122         if (is_null($result)) {
3123             return NULL;
3124         }
3125         $i = 0;
3126         $currentsetting = array();
3127         $items = explode(';', $result);
3128         foreach ($items as $item) {
3129             $item = explode(':', $item);
3130             $currentsetting['k'.$i] = $item[0];
3131             $currentsetting['v'.$i] = $item[1];
3132             $i++;
3133         }
3134         return $currentsetting;
3135     }
3137     /**
3138      * Save the new setting(s)
3139      *
3140      * @todo Add vartype handling to ensure $data is an array
3141      * @param array $data Array containing the new settings
3142      * @return bool
3143      */
3144     public function write_setting($data) {
3146     // there miiight be an easier way to do this :)
3147     // if this is changed, make sure the $defaults array above is modified so that this
3148     // function processes it correctly
3150         $keys = array();
3151         $values = array();
3153         foreach ($data as $key => $value) {
3154             if (substr($key,0,1) == 'k') {
3155                 $keys[substr($key,1)] = $value;
3156             } elseif (substr($key,0,1) == 'v') {
3157                 $values[substr($key,1)] = $value;
3158             }
3159         }
3161         $result = array();
3162         for ($i = 0; $i < count($keys); $i++) {
3163             if (($keys[$i] !== '') && ($values[$i] !== '')) {
3164                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).':'.clean_param($values[$i], PARAM_NOTAGS);
3165             }
3166         }
3168         return ($this->config_write($this->name, implode(';', $result)) ? '' : get_string('errorsetting', 'admin'));
3169     }
3171     /**
3172      * Returns XHTML for the options
3173      *
3174      * @todo Add vartype handling to ensure that $data is an array
3175      * @param array $data An array of values to set
3176      * @param string $query
3177      * @return string XHTML
3178      */
3179     public function output_html($data, $query='') {
3180         $fullname = $this->get_full_name();
3181         $return = '<div class="form-group">';
3182         for ($i = 0; $i < count($data) / 2; $i++) {
3183             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
3184             $return .= '&nbsp;&nbsp;';
3185             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
3186         }
3187         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
3188         $return .= '&nbsp;&nbsp;';
3189         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3190         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
3191         $return .= '&nbsp;&nbsp;';
3192         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
3193         $return .= '</div>';
3195         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3196     }
3199 /**
3200  * Special settings for emoticons
3201  *
3202  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3203  */
3204 class admin_setting_emoticons extends admin_setting {
3206 /**
3207  * Calls parent::__construct with specific args
3208  */
3209     public function __construct() {
3210         global $CFG;
3211         $name = 'emoticons';
3212         $visiblename = get_string('emoticons', 'admin');
3213         $description = get_string('configemoticons', 'admin');
3214         $defaults = array('k0' => ':-)',
3215             'v0' => 'smiley',
3216             'k1' => ':)',
3217             'v1' => 'smiley',
3218             'k2' => ':-D',
3219             'v2' => 'biggrin',
3220             'k3' => ';-)',
3221             'v3' => 'wink',
3222             'k4' => ':-/',
3223             'v4' => 'mixed',
3224             'k5' => 'V-.',
3225             'v5' => 'thoughtful',
3226             'k6' => ':-P',
3227             'v6' => 'tongueout',
3228             'k7' => 'B-)',
3229             'v7' => 'cool',
3230             'k8' => '^-)',
3231             'v8' => 'approve',
3232             'k9' => '8-)',
3233             'v9' => 'wideeyes',
3234             'k10' => ':o)',
3235             'v10' => 'clown',
3236             'k11' => ':-(',
3237             'v11' => 'sad',
3238             'k12' => ':(',
3239             'v12' => 'sad',
3240             'k13' => '8-.',
3241             'v13' => 'shy',
3242             'k14' => ':-I',
3243             'v14' => 'blush',
3244             'k15' => ':-X',
3245             'v15' => 'kiss',
3246             'k16' => '8-o',
3247             'v16' => 'surprise',
3248             'k17' => 'P-|',
3249             'v17' => 'blackeye',
3250             'k18' => '8-[',
3251             'v18' => 'angry',
3252             'k19' => 'xx-P',
3253             'v19' => 'dead',
3254             'k20' => '|-.',
3255             'v20' => 'sleepy',
3256             'k21' => '}-]',
3257             'v21' => 'evil',
3258             'k22' => '(h)',
3259             'v22' => 'heart',
3260             'k23' => '(heart)',
3261             'v23' => 'heart',
3262             'k24' => '(y)',
3263             'v24' => 'yes',
3264             'k25' => '(n)',
3265             'v25' => 'no',
3266             'k26' => '(martin)',
3267             'v26' => 'martin',
3268             'k27' => '( )',
3269             'v27' => 'egg');
3270         parent::__construct($name, $visiblename, $description, $defaults);
3271     }
3273     /**
3274      * Return the current setting(s)
3275      *
3276      * @return array Current settings array
3277      */
3278     public function get_setting() {
3279         global $CFG;
3280         $result = $this->config_read($this->name);
3281         if (is_null($result)) {
3282             return NULL;
3283         }
3284         $i = 0;
3285         $currentsetting = array();
3286         $items = explode('{;}', $result);
3287         foreach ($items as $item) {
3288             $item = explode('{:}', $item);
3289             $currentsetting['k'.$i] = $item[0];
3290             $currentsetting['v'.$i] = $item[1];
3291             $i++;
3292         }
3293         return $currentsetting;
3294     }
3296     /**
3297      * Save selected settings
3298      *
3299      * @param array $data Array of settings to save
3300      * @return bool
3301      */
3302     public function write_setting($data) {
3304     // there miiight be an easier way to do this :)
3305     // if this is changed, make sure the $defaults array above is modified so that this
3306     // function processes it correctly
3308         $keys = array();
3309         $values = array();
3311         foreach ($data as $key => $value) {
3312             if (substr($key,0,1) == 'k') {
3313                 $keys[substr($key,1)] = $value;
3314             } elseif (substr($key,0,1) == 'v') {
3315                 $values[substr($key,1)] = $value;
3316             }
3317         }
3319         $result = array();
3320         for ($i = 0; $i < count($keys); $i++) {
3321             if (($keys[$i] !== '') && ($values[$i] !== '')) {
3322                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).'{:}'.clean_param($values[$i], PARAM_NOTAGS);
3323             }
3324         }
3326         return ($this->config_write($this->name, implode('{;}', $result)) ? '' : get_string('errorsetting', 'admin').$this->visiblename.'<br />');
3327     }
3329     /**
3330      * Return XHTML field(s) for options
3331      *
3332      * @param array $data Array of options to set in HTML
3333      * @return string XHTML string for the fields and wrapping div(s)
3334      */
3335     public function output_html($data, $query='') {
3336         $fullname = $this->get_full_name();
3337         $return = '<div class="form-group">';
3338         for ($i = 0; $i < count($data) / 2; $i++) {
3339             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
3340             $return .= '&nbsp;&nbsp;';
3341             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
3342         }
3343         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
3344         $return .= '&nbsp;&nbsp;';
3345         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3346         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
3347         $return .= '&nbsp;&nbsp;';
3348         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
3349         $return .= '</div>';
3351         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3352     }
3355 /**
3356  * Used to set editor options/settings
3357  *
3358  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3359  */
3361 class admin_setting_special_editorhidebuttons extends admin_setting {
3362 /** @var array Array of possible options */
3363     public $items;
3365     /**
3366      * Calls parent::__construct with specific options
3367      */
3368     public function __construct() {
3369         parent::__construct('editorhidebuttons', get_string('editorhidebuttons', 'admin'),
3370             get_string('confeditorhidebuttons', 'admin'), array());
3371         // weird array... buttonname => buttonimage (assume proper path appended). if you leave buttomimage blank, text will be printed instead
3372         $this->items = array('fontname' => '',
3373             'fontsize' => '',
3374             'formatblock' => '',
3375             'bold' => 'ed_format_bold.gif',
3376             'italic' => 'ed_format_italic.gif',
3377             'underline' => 'ed_format_underline.gif',
3378             'strikethrough' => 'ed_format_strike.gif',
3379             'subscript' => 'ed_format_sub.gif',
3380             'superscript' => 'ed_format_sup.gif',
3381             'copy' => 'ed_copy.gif',
3382             'cut' => 'ed_cut.gif',
3383             'paste' => 'ed_paste.gif',
3384             'clean' => 'ed_wordclean.gif',
3385             'undo' => 'ed_undo.gif',
3386             'redo' => 'ed_redo.gif',
3387             'justifyleft' => 'ed_align_left.gif',
3388             'justifycenter' => 'ed_align_center.gif',
3389             'justifyright' => 'ed_align_right.gif',
3390             'justifyfull' => 'ed_align_justify.gif',
3391             'lefttoright' => 'ed_left_to_right.gif',
3392             'righttoleft' => 'ed_right_to_left.gif',
3393             'insertorderedlist' => 'ed_list_num.gif',
3394             'insertunorderedlist' => 'ed_list_bullet.gif',
3395             'outdent' => 'ed_indent_less.gif',
3396             'indent' => 'ed_indent_more.gif',
3397             'forecolor' => 'ed_color_fg.gif',
3398             'hilitecolor' => 'ed_color_bg.gif',
3399             'inserthorizontalrule' => 'ed_hr.gif',
3400             'createanchor' => 'ed_anchor.gif',
3401             'createlink' => 'ed_link.gif',
3402             'unlink' => 'ed_unlink.gif',
3403             'insertimage' => 'ed_image.gif',
3404             'inserttable' => 'insert_table.gif',
3405             'insertsmile' => 'em.icon.smile.gif',
3406             'insertchar' => 'icon_ins_char.gif',
3407             'spellcheck' => 'spell-check.gif',
3408             'htmlmode' => 'ed_html.gif',
3409             'popupeditor' => 'fullscreen_maximize.gif',
3410             'search_replace' => 'ed_replace.gif');
3411     }
3413     /**
3414      * Get an array of current settings
3415      *
3416      * @return array Array of current settings
3417      */
3418     public function get_setting() {
3419         $result = $this->config_read($this->name);
3420         if (is_null($result)) {
3421             return NULL;
3422         }
3423         if ($result === '') {
3424             return array();
3425         }
3426         return explode(' ', $result);
3427     }
3429     /**
3430      * Save the selected settings
3431      *
3432      * @param array $data Array of settings to save
3433      * @return mixed empty string, error string, or bool true=>success, false=>error
3434      */
3435     public function write_setting($data) {
3436         if (!is_array($data)) {
3437             return ''; // ignore it
3438         }
3439         unset($data['xxxxx']);
3440         $result = array();
3442         foreach ($data as $key => $value) {
3443             if (!isset($this->items[$key])) {
3444                 return get_string('errorsetting', 'admin');
3445             }
3446             if ($value == '1') {
3447                 $result[] = $key;
3448             }
3449         }
3450         return ($this->config_write($this->name, implode(' ', $result)) ? '' : get_string('errorsetting', 'admin'));
3451     }
3453     /**
3454      * Return XHTML for the field and wrapping div(s)
3455      *
3456      * @param array $data
3457      * @param string $query
3458      * @return string XHTML for output
3459      */
3460     public function output_html($data, $query='') {
3462         global $CFG;
3464         // checkboxes with input name="$this->name[$key]" value="1"
3465         // we do 15 fields per column
3467         $return = '<div class="form-group">';
3468         $return .= '<table><tr><td valign="top" align="right">';
3469         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3471         $count = 0;
3473         foreach($this->items as $key => $value) {
3474             if ($count % 15 == 0 and $count != 0) {
3475                 $return .= '</td><td valign="top" align="right">';
3476             }
3478             $return .= '<label for="'.$this->get_id().$key.'">';
3479             $return .= ($value == '' ? get_string($key,'editor') : '<img width="18" height="18" src="'.$CFG->wwwroot.'/lib/editor/htmlarea/images/'.$value.'" alt="'.get_string($key,'editor').'" title="'.get_string($key,'editor').'" />').'&nbsp;';
3480             $return .= '<input type="checkbox" class="form-checkbox" value="1" id="'.$this->get_id().$key.'" name="'.$this->get_full_name().'['.$key.']"'.(in_array($key,$data) ? ' checked="checked"' : '').' />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
3481             $return .= '</label>';
3482             $count++;
3483             if ($count % 15 != 0) {
3484                 $return .= '<br /><br />';
3485             }
3486         }
3488         $return .= '</td></tr>';
3489         $return .= '</table>';
3490         $return .= '</div>';
3492         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3493     }
3496 /**
3497  * Special setting for limiting of the list of available languages.
3498  *
3499  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3500  */
3501 class admin_setting_langlist extends admin_setting_configtext {
3502 /**
3503  * Calls parent::__construct with specific arguments
3504  */
3505     public function __construct() {
3506         parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
3507     }
3509     /**
3510      * Save the new setting
3511      *
3512      * @param string $data The new setting
3513      * @return bool
3514      */
3515     public function write_setting($data) {
3516         $return = parent::write_setting($data);
3517         get_list_of_languages(true);//refresh the list
3518         return $return;
3519     }
3522 /**
3523  * Selection of one of the recognised countries using the list
3524  * returned by {@link get_list_of_countries()}.
3525  *
3526  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3527  */
3528 class admin_settings_country_select extends admin_setting_configselect {
3529     public function __construct($name, $visiblename, $description, $defaultsetting) {
3530         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
3531     }
3533     /**
3534      * Lazy-load the available choices for the select box
3535      */
3536     public function load_choices() {
3537         global $CFG;
3538         if (is_array($this->choices)) {
3539             return true;
3540         }
3541         $this->choices = array_merge(
3542                 array('0' => get_string('choosedots')),
3543                 get_list_of_countries());
3544         return true;
3545     }
3548 /**
3549  * Course category selection
3550  *
3551  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3552  */
3553 class admin_settings_coursecat_select extends admin_setting_configselect {
3554 /**
3555  * Calls parent::__construct with specific arguments
3556  */
3557     public function __construct($name, $visiblename, $description, $defaultsetting) {
3558         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
3559     }
3561     /**
3562      * Load the available choices for the select box
3563      *
3564      * @return bool
3565      */
3566     public function load_choices() {
3567         global $CFG;
3568         require_once($CFG->dirroot.'/course/lib.php');
3569         if (is_array($this->choices)) {
3570             return true;
3571         }
3572         $this->choices = make_categories_options();
3573         return true;
3574     }
3577 /**
3578  * Special control for selecting days to backup
3579  *
3580  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3581  */
3582 class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
3583 /**
3584  * Calls parent::__construct with specific arguments
3585  */
3586     public function __construct() {
3587         parent::__construct('backup_sche_weekdays', get_string('schedule'), get_string('backupschedulehelp'), array(), NULL);
3588         $this->plugin = 'backup';
3589     }
3590     /**
3591      * Load the available choices for the select box
3592      *
3593      * @return bool Always returns true
3594      */
3595     public function load_choices() {
3596         if (is_array($this->choices)) {
3597             return true;
3598         }
3599         $this->choices = array();
3600         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
3601         foreach ($days as $day) {
3602             $this->choices[$day] = get_string($day, 'calendar');
3603         }
3604         return true;
3605     }
3608 /**
3609  * Special debug setting
3610  *
3611  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3612  */
3613 class admin_setting_special_debug extends admin_setting_configselect {
3614 /**
3615  * Calls parent::__construct with specific arguments
3616  */
3617     public function __construct() {
3618         parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
3619     }
3621     /**
3622      * Load the available choices for the select box
3623      *
3624      * @return bool
3625      */
3626     public function load_choices() {
3627         if (is_array($this->choices)) {
3628             return true;
3629         }
3630         $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
3631             DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
3632             DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
3633             DEBUG_ALL       => get_string('debugall', 'admin'),
3634             DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
3635         return true;
3636     }
3639 /**
3640  * Special admin control
3641  *
3642  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3643  */
3644 class admin_setting_special_calendar_weekend extends admin_setting {
3645 /**
3646  * Calls parent::__construct with specific arguments
3647  */
3648     public function __construct() {
3649         $name = 'calendar_weekend';
3650         $visiblename = get_string('calendar_weekend', 'admin');
3651         $description = get_string('helpweekenddays', 'admin');
3652         $default = array ('0', '6'); // Saturdays and Sundays
3653         parent::__construct($name, $visiblename, $description, $default);
3654     }
3655     /**
3656      * Gets the current settins as an array
3657      *
3658      * @return mixed Null if none, else array of settings
3659      */
3660     public function get_setting() {
3661         $result = $this->config_read($this->name);
3662         if (is_null($result)) {
3663             return NULL;
3664         }
3665         if ($result === '') {
3666             return array();
3667         }
3668         $settings = array();
3669         for ($i=0; $i<7; $i++) {
3670             if ($result & (1 << $i)) {
3671                 $settings[] = $i;
3672             }
3673         }
3674         return $settings;
3675     }
3677     /**
3678      * Save the new settings
3679      *
3680      * @param array $data Array of new settings