moodle_page: MDL-12212 reimplement user_is_editing, deprecate isediting
[moodle.git] / lib / adminlib.php
1 <?php  //$Id$
3 /**
4  * adminlib.php - Contains functions that only administrators will ever need to use
5  *
6  * @author Martin Dougiamas and many others
7  * @version  $Id$
8  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
9  * @package moodlecore
10  */
12 /// Add libraries
13 require_once($CFG->libdir.'/ddllib.php');
14 require_once($CFG->libdir.'/xmlize.php');
15 require_once($CFG->libdir.'/messagelib.php');      // Messagelib functions
17 define('INSECURE_DATAROOT_WARNING', 1);
18 define('INSECURE_DATAROOT_ERROR', 2);
20 /**
21  * Delete all plugin tables
22  * @name string name of plugin, used as table prefix
23  * @file string path to install.xml file
24  * @feedback boolean
25  */
26 function drop_plugin_tables($name, $file, $feedback=true) {
27     global $CFG, $DB;
29     // first try normal delete
30     if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
31         return true;
32     }
34     // then try to find all tables that start with name and are not in any xml file
35     $used_tables = get_used_table_names();
37     $tables = $DB->get_tables();
39     /// Iterate over, fixing id fields as necessary
40     foreach ($tables as $table) {
41         if (in_array($table, $used_tables)) {
42             continue;
43         }
45         if (strpos($table, $name) !== 0) {
46             continue;
47         }
49         // found orphan table --> delete it
50         if ($DB->get_manager()->table_exists($table)) {
51             $xmldb_table = new xmldb_table($table);
52             $DB->get_manager()->drop_table($xmldb_table);
53         }
54     }
56     return true;
57 }
59 /**
60  * Returns names of all known tables == tables that moodle knowns about.
61  * @return array of lowercase table names
62  */
63 function get_used_table_names() {
64     $table_names = array();
65     $dbdirs = get_db_directories();
67     foreach ($dbdirs as $dbdir) {
68         $file = $dbdir.'/install.xml';
70         $xmldb_file = new xmldb_file($file);
72         if (!$xmldb_file->fileExists()) {
73             continue;
74         }
76         $loaded    = $xmldb_file->loadXMLStructure();
77         $structure = $xmldb_file->getStructure();
79         if ($loaded and $tables = $structure->getTables()) {
80             foreach($tables as $table) {
81                 $table_names[] = strtolower($table->name);
82             }
83         }
84     }
86     return $table_names;
87 }
89 /**
90  * Lists all plugin types
91  * @return array of strings - name=>location
92  */
93 function get_plugin_types() {
94     global $CFG;
96     return array('mod'           => 'mod',
97                  'qtype'         => 'question/type',
98                  'block'         => 'blocks',
99                  'auth'          => 'auth',
100                  'enrol'         => 'enrol',
101                  'format'        => 'course/format',
102                  'gradeexport'   => 'grade/export',
103                  'gradeimport'   => 'grade/import',
104                  'gradereport'   => 'grade/report',
105                  'message'       => 'message/output',
106                  'coursereport'  => 'course/report',
107                  'report'        => $CFG->admin.'/report',
108                  'portfolio'     => 'portfolio/type',
109                  'repository'    => 'repository',
111         // following types a very ugly hacks - we should not make exceptions like this - all plugins should be equal;
112         // these plugins may cause problems such as when wanting to uninstall them
113                  'quizreport'      => 'mod/quiz/report',
114                  'assignment_type' => 'mod/assignment/type',
115                 );
118 /**
119  * Returns list of all directories where we expect install.xml files
120  * @return array of paths
121  */
122 function get_db_directories() {
123     global $CFG;
125     $dbdirs = array();
127 /// First, the main one (lib/db)
128     $dbdirs[] = $CFG->libdir.'/db';
130 /// Now, activity modules (mod/xxx/db)
131     if ($plugins = get_list_of_plugins('mod')) {
132         foreach ($plugins as $plugin) {
133             $dbdirs[] = $CFG->dirroot.'/mod/'.$plugin.'/db';
134         }
135     }
137 /// Now, assignment submodules (mod/assignment/type/xxx/db)
138     if ($plugins = get_list_of_plugins('mod/assignment/type')) {
139         foreach ($plugins as $plugin) {
140             $dbdirs[] = $CFG->dirroot.'/mod/assignment/type/'.$plugin.'/db';
141         }
142     }
144 /// Now, question types (question/type/xxx/db)
145     if ($plugins = get_list_of_plugins('question/type')) {
146         foreach ($plugins as $plugin) {
147             $dbdirs[] = $CFG->dirroot.'/question/type/'.$plugin.'/db';
148         }
149     }
151 /// Now, blocks (blocks/xxx/db)
152     if ($plugins = get_list_of_plugins('blocks', 'db')) {
153         foreach ($plugins as $plugin) {
154             $dbdirs[] = $CFG->dirroot.'/blocks/'.$plugin.'/db';
155         }
156     }
158 /// Now, course formats (course/format/xxx/db)
159     if ($plugins = get_list_of_plugins('course/format', 'db')) {
160         foreach ($plugins as $plugin) {
161             $dbdirs[] = $CFG->dirroot.'/course/format/'.$plugin.'/db';
162         }
163     }
165 /// Now, enrolment plugins (enrol/xxx/db)
166     if ($plugins = get_list_of_plugins('enrol', 'db')) {
167         foreach ($plugins as $plugin) {
168             $dbdirs[] = $CFG->dirroot.'/enrol/'.$plugin.'/db';
169         }
170     }
172 /// Now admin report plugins (admin/report/xxx/db)
173     if ($plugins = get_list_of_plugins($CFG->admin.'/report', 'db')) {
174         foreach ($plugins as $plugin) {
175             $dbdirs[] = $CFG->dirroot.'/'.$CFG->admin.'/report/'.$plugin.'/db';
176         }
177     }
179 /// Now quiz report plugins (mod/quiz/report/xxx/db)
180     if ($plugins = get_list_of_plugins('mod/quiz/report', 'db')) {
181         foreach ($plugins as $plugin) {
182             $dbdirs[] = $CFG->dirroot.'/mod/quiz/report/'.$plugin.'/db';
183         }
184     }
186     if ($plugins = get_list_of_plugins('portfolio/type', 'db')) {
187         foreach ($plugins as $plugin) {
188             $dbdirs[] = $CFG->dirroot . '/portfolio/type/' . $plugin . '/db';
189         }
190     }
192 /// Local database changes, if the local folder exists.
193     if (file_exists($CFG->dirroot . '/local')) {
194         $dbdirs[] = $CFG->dirroot.'/local/db';
195     }
197     return $dbdirs;
200 /**
201  * Try to obtain or release the cron lock.
202  *
203  * @param string  $name  name of lock
204  * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionaly
205  * @param bool $ignorecurrent ignore current lock state, usually entend previous lock
206  * @return bool true if lock obtained
207  */
208 function set_cron_lock($name, $until, $ignorecurrent=false) {
209     global $DB;
210     if (empty($name)) {
211         debugging("Tried to get a cron lock for a null fieldname");
212         return false;
213     }
215     // remove lock by force == remove from config table
216     if (is_null($until)) {
217         set_config($name, null);
218         return true;
219     }
221     if (!$ignorecurrent) {
222         // read value from db - other processes might have changed it
223         $value = $DB->get_field('config', 'value', array('name'=>$name));
225         if ($value and $value > time()) {
226             //lock active
227             return false;
228         }
229     }
231     set_config($name, $until);
232     return true;
235 /**
236  * Test if and critical warnings are present
237  * @return bool
238  */
239 function admin_critical_warnings_present() {
240     global $SESSION;
242     if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
243         return 0;
244     }
246     if (!isset($SESSION->admin_critical_warning)) {
247         $SESSION->admin_critical_warning = 0;
248         if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
249             $SESSION->admin_critical_warning = 1;
250         }
251     }
253     return $SESSION->admin_critical_warning;
256 /**
257  * Detects if float support at least 10 deciman digits
258  * and also if float-->string conversion works as expected.
259  * @return bool true if problem found
260  */
261 function is_float_problem() {
262     $num1 = 2009010200.01;
263     $num2 = 2009010200.02;
265     return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
268 /**
269  * Try to verify that dataroot is not accessible from web.
270  * It is not 100% correct but might help to reduce number of vulnerable sites.
271  *
272  * Protection from httpd.conf and .htaccess is not detected properly.
273  * @param bool $fetchtest try to test public access by fetching file
274  * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING migth be problematic
275  */
276 function is_dataroot_insecure($fetchtest=false) {
277     global $CFG;
279     $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
281     $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
282     $rp = strrev(trim($rp, '/'));
283     $rp = explode('/', $rp);
284     foreach($rp as $r) {
285         if (strpos($siteroot, '/'.$r.'/') === 0) {
286             $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
287         } else {
288             break; // probably alias root
289         }
290     }
292     $siteroot = strrev($siteroot);
293     $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
295     if (strpos($dataroot, $siteroot) !== 0) {
296         return false;
297     }
299     if (!$fetchtest) {
300         return INSECURE_DATAROOT_WARNING;
301     }
303     // now try all methods to fetch a test file using http protocol
305     $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
306     preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
307     $httpdocroot = $matches[1];
308     $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
309     if (make_upload_directory('diag', false) === false) {
310         return INSECURE_DATAROOT_WARNING;
311     }
312     $testfile = $CFG->dataroot.'/diag/public.txt';
313     if (!file_exists($testfile)) {
314         file_put_contents($testfile, 'test file, do not delete');
315     }
316     $teststr = trim(file_get_contents($testfile));
317     if (empty($teststr)) {
318         // hmm, strange
319         return INSECURE_DATAROOT_WARNING;
320     }
322     $testurl = $datarooturl.'/diag/public.txt';
323     if (extension_loaded('curl') and
324         !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
325         !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
326         ($ch = @curl_init($testurl)) !== false) {
327         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
328         curl_setopt($ch, CURLOPT_HEADER, false);
329         $data = curl_exec($ch);
330         if (!curl_errno($ch)) {
331             $data = trim($data);
332             if ($data === $teststr) {
333                 curl_close($ch);
334                 return INSECURE_DATAROOT_ERROR;
335             }
336         }
337         curl_close($ch);
338     }
340     if ($data = @file_get_contents($testurl)) {
341         $data = trim($data);
342         if ($data === $teststr) {
343             return INSECURE_DATAROOT_ERROR;
344         }
345     }
347     preg_match('|https?://([^/]+)|i', $testurl, $matches);
348     $sitename = $matches[1];
349     $error = 0;
350     if ($fp = @fsockopen($sitename, 80, $error)) {
351         preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
352         $localurl = $matches[1];
353         $out = "GET $localurl HTTP/1.1\r\n";
354         $out .= "Host: $sitename\r\n";
355         $out .= "Connection: Close\r\n\r\n";
356         fwrite($fp, $out);
357         $data = '';
358         $incoming = false;
359         while (!feof($fp)) {
360             if ($incoming) {
361                 $data .= fgets($fp, 1024);
362             } else if (@fgets($fp, 1024) === "\r\n") {
363                 $incoming = true;
364             }
365         }
366         fclose($fp);
367         $data = trim($data);
368         if ($data === $teststr) {
369             return INSECURE_DATAROOT_ERROR;
370         }
371     }
373     return INSECURE_DATAROOT_WARNING;
376 /// =============================================================================================================
377 /// administration tree classes and functions
380 // n.b. documentation is still in progress for this code
382 /// INTRODUCTION
384 /// This file performs the following tasks:
385 ///  -it defines the necessary objects and interfaces to build the Moodle
386 ///   admin hierarchy
387 ///  -it defines the admin_externalpage_setup(), admin_externalpage_print_header(),
388 ///   and admin_externalpage_print_footer() functions used on admin pages
390 /// ADMIN_SETTING OBJECTS
392 /// Moodle settings are represented by objects that inherit from the admin_setting
393 /// class. These objects encapsulate how to read a setting, how to write a new value
394 /// to a setting, and how to appropriately display the HTML to modify the setting.
396 /// ADMIN_SETTINGPAGE OBJECTS
398 /// The admin_setting objects are then grouped into admin_settingpages. The latter
399 /// appear in the Moodle admin tree block. All interaction with admin_settingpage
400 /// objects is handled by the admin/settings.php file.
402 /// ADMIN_EXTERNALPAGE OBJECTS
404 /// There are some settings in Moodle that are too complex to (efficiently) handle
405 /// with admin_settingpages. (Consider, for example, user management and displaying
406 /// lists of users.) In this case, we use the admin_externalpage object. This object
407 /// places a link to an external PHP file in the admin tree block.
409 /// If you're using an admin_externalpage object for some settings, you can take
410 /// advantage of the admin_externalpage_* functions. For example, suppose you wanted
411 /// to add a foo.php file into admin. First off, you add the following line to
412 /// admin/settings/first.php (at the end of the file) or to some other file in
413 /// admin/settings:
415 ///    $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
416 ///        $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
418 /// Next, in foo.php, your file structure would resemble the following:
420 ///        require_once('.../config.php');
421 ///        require_once($CFG->libdir.'/adminlib.php');
422 ///        admin_externalpage_setup('foo');
423 ///        // functionality like processing form submissions goes here
424 ///        admin_externalpage_print_header();
425 ///        // your HTML goes here
426 ///        admin_externalpage_print_footer();
428 /// The admin_externalpage_setup() function call ensures the user is logged in,
429 /// and makes sure that they have the proper role permission to access the page.
431 /// The admin_externalpage_print_header() function prints the header (it figures
432 /// out what category and subcategories the page is classified under) and ensures
433 /// that you're using the admin pagelib (which provides the admin tree block and
434 /// the admin bookmarks block).
436 /// The admin_externalpage_print_footer() function properly closes the tables
437 /// opened up by the admin_externalpage_print_header() function and prints the
438 /// standard Moodle footer.
440 /// ADMIN_CATEGORY OBJECTS
442 /// Above and beyond all this, we have admin_category objects. These objects
443 /// appear as folders in the admin tree block. They contain admin_settingpage's,
444 /// admin_externalpage's, and other admin_category's.
446 /// OTHER NOTES
448 /// admin_settingpage's, admin_externalpage's, and admin_category's all inherit
449 /// from part_of_admin_tree (a pseudointerface). This interface insists that
450 /// a class has a check_access method for access permissions, a locate method
451 /// used to find a specific node in the admin tree and find parent path.
453 /// admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
454 /// interface ensures that the class implements a recursive add function which
455 /// accepts a part_of_admin_tree object and searches for the proper place to
456 /// put it. parentable_part_of_admin_tree implies part_of_admin_tree.
458 /// Please note that the $this->name field of any part_of_admin_tree must be
459 /// UNIQUE throughout the ENTIRE admin tree.
461 /// The $this->name field of an admin_setting object (which is *not* part_of_
462 /// admin_tree) must be unique on the respective admin_settingpage where it is
463 /// used.
466 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
468 /**
469  * Pseudointerface for anything appearing in the admin tree
470  *
471  * The pseudointerface that is implemented by anything that appears in the admin tree
472  * block. It forces inheriting classes to define a method for checking user permissions
473  * and methods for finding something in the admin tree.
474  *
475  * @author Vincenzo K. Marcovecchio
476  * @package admin
477  */
478 interface part_of_admin_tree {
480     /**
481      * Finds a named part_of_admin_tree.
482      *
483      * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
484      * and not parentable_part_of_admin_tree, then this function should only check if
485      * $this->name matches $name. If it does, it should return a reference to $this,
486      * otherwise, it should return a reference to NULL.
487      *
488      * If a class inherits parentable_part_of_admin_tree, this method should be called
489      * recursively on all child objects (assuming, of course, the parent object's name
490      * doesn't match the search criterion).
491      *
492      * @param string $name The internal name of the part_of_admin_tree we're searching for.
493      * @return mixed An object reference or a NULL reference.
494      */
495     public function locate($name);
497     /**
498      * Removes named part_of_admin_tree.
499      *
500      * @param string $name The internal name of the part_of_admin_tree we want to remove.
501      * @return bool success.
502      */
503     public function prune($name);
505     /**
506      * Search using query
507      * @param strin query
508      * @return mixed array-object structure of found settings and pages
509      */
510     public function search($query);
512     /**
513      * Verifies current user's access to this part_of_admin_tree.
514      *
515      * Used to check if the current user has access to this part of the admin tree or
516      * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
517      * then this method is usually just a call to has_capability() in the site context.
518      *
519      * If a class inherits parentable_part_of_admin_tree, this method should return the
520      * logical OR of the return of check_access() on all child objects.
521      *
522      * @return bool True if the user has access, false if she doesn't.
523      */
524     public function check_access();
526     /**
527      * Mostly usefull for removing of some parts of the tree in admin tree block.
528      *
529      * @return True is hidden from normal list view
530      */
531     public function is_hidden();
534 /**
535  * Pseudointerface implemented by any part_of_admin_tree that has children.
536  *
537  * The pseudointerface implemented by any part_of_admin_tree that can be a parent
538  * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
539  * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
540  * include an add method for adding other part_of_admin_tree objects as children.
541  *
542  * @author Vincenzo K. Marcovecchio
543  * @package admin
544  */
545 interface parentable_part_of_admin_tree extends part_of_admin_tree {
547     /**
548      * Adds a part_of_admin_tree object to the admin tree.
549      *
550      * Used to add a part_of_admin_tree object to this object or a child of this
551      * object. $something should only be added if $destinationname matches
552      * $this->name. If it doesn't, add should be called on child objects that are
553      * also parentable_part_of_admin_tree's.
554      *
555      * @param string $destinationname The internal name of the new parent for $something.
556      * @param part_of_admin_tree $something The object to be added.
557      * @return bool True on success, false on failure.
558      */
559     public function add($destinationname, $something);
563 /**
564  * The object used to represent folders (a.k.a. categories) in the admin tree block.
565  *
566  * Each admin_category object contains a number of part_of_admin_tree objects.
567  *
568  * @author Vincenzo K. Marcovecchio
569  * @package admin
570  */
571 class admin_category implements parentable_part_of_admin_tree {
573     /**
574      * @var mixed An array of part_of_admin_tree objects that are this object's children
575      */
576     public $children;
578     /**
579      * @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
580      */
581     public $name;
583     /**
584      * @var string The displayed name for this category. Usually obtained through get_string()
585      */
586     public $visiblename;
588     /**
589      * @var bool Should this category be hidden in admin tree block?
590      */
591     public $hidden;
593     /**
594      * paths
595      */
596     public $path;
597     public $visiblepath;
599     /**
600      * Constructor for an empty admin category
601      *
602      * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
603      * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
604      * @param bool $hidden hide category in admin tree block
605      */
606     public function __construct($name, $visiblename, $hidden=false) {
607         $this->children    = array();
608         $this->name        = $name;
609         $this->visiblename = $visiblename;
610         $this->hidden      = $hidden;
611     }
613     /**
614      * Returns a reference to the part_of_admin_tree object with internal name $name.
615      *
616      * @param string $name The internal name of the object we want.
617      * @param bool $findpath initialize path and visiblepath arrays
618      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
619      */
620     public function locate($name, $findpath=false) {
621         if ($this->name == $name) {
622             if ($findpath) {
623                 $this->visiblepath[] = $this->visiblename;
624                 $this->path[]        = $this->name;
625             }
626             return $this;
627         }
629         $return = NULL;
630         foreach($this->children as $childid=>$unused) {
631             if ($return = $this->children[$childid]->locate($name, $findpath)) {
632                 break;
633             }
634         }
636         if (!is_null($return) and $findpath) {
637             $return->visiblepath[] = $this->visiblename;
638             $return->path[]        = $this->name;
639         }
641         return $return;
642     }
644     /**
645      * Search using query
646      * @param strin query
647      * @return mixed array-object structure of found settings and pages
648      */
649     public function search($query) {
650         $result = array();
651         foreach ($this->children as $child) {
652             $subsearch = $child->search($query);
653             if (!is_array($subsearch)) {
654                 debugging('Incorrect search result from '.$child->name);
655                 continue;
656             }
657             $result = array_merge($result, $subsearch);
658         }
659         return $result;
660     }
662     /**
663      * Removes part_of_admin_tree object with internal name $name.
664      *
665      * @param string $name The internal name of the object we want to remove.
666      * @return bool success
667      */
668     public function prune($name) {
670         if ($this->name == $name) {
671             return false;  //can not remove itself
672         }
674         foreach($this->children as $precedence => $child) {
675             if ($child->name == $name) {
676                 // found it!
677                 unset($this->children[$precedence]);
678                 return true;
679             }
680             if ($this->children[$precedence]->prune($name)) {
681                 return true;
682             }
683         }
684         return false;
685     }
687     /**
688      * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
689      *
690      * @param string $destinationame The internal name of the immediate parent that we want for $something.
691      * @param mixed $something A part_of_admin_tree or setting instanceto be added.
692      * @return bool True if successfully added, false if $something can not be added.
693      */
694     public function add($parentname, $something) {
695         $parent = $this->locate($parentname);
696         if (is_null($parent)) {
697             debugging('parent does not exist!');
698             return false;
699         }
701         if ($something instanceof part_of_admin_tree) {
702             if (!($parent instanceof parentable_part_of_admin_tree)) {
703                 debugging('error - parts of tree can be inserted only into parentable parts');
704                 return false;
705             }
706             $parent->children[] = $something;
707             return true;
709         } else {
710             debugging('error - can not add this element');
711             return false;
712         }
714     }
716     /**
717      * Checks if the user has access to anything in this category.
718      *
719      * @return bool True if the user has access to atleast one child in this category, false otherwise.
720      */
721     public function check_access() {
722         foreach ($this->children as $child) {
723             if ($child->check_access()) {
724                 return true;
725             }
726         }
727         return false;
728     }
730     /**
731      * Is this category hidden in admin tree block?
732      *
733      * @return bool True if hidden
734      */
735     public function is_hidden() {
736         return $this->hidden;
737     }
740 class admin_root extends admin_category {
741     /** list of errors */
742     public $errors;
744     /** search query */
745     public $search;
747     /** full tree flag - true means all settings required, false onlypages required */
748     public $fulltree;
750     /** flag indicating loaded tree */
751     public $loaded;
753     /** site custom defaults overriding defaults in setings files */
754     public $custom_defaults;
756     public function __construct($fulltree) {
757         global $CFG;
759         parent::__construct('root', get_string('administration'), false);
760         $this->errors   = array();
761         $this->search   = '';
762         $this->fulltree = $fulltree;
763         $this->loaded   = false;
765         // load custom defaults if found
766         $this->custom_defaults = null;
767         $defaultsfile = "$CFG->dirroot/local/defaults.php";
768         if (is_readable($defaultsfile)) {
769             $defaults = array();
770             include($defaultsfile);
771             if (is_array($defaults) and count($defaults)) {
772                 $this->custom_defaults = $defaults;
773             }
774         }
775     }
777     public function purge_children($requirefulltree) {
778         $this->children = array();
779         $this->fulltree = ($requirefulltree || $this->fulltree);
780         $this->loaded   = false;
781     }
784 /**
785  * Links external PHP pages into the admin tree.
786  *
787  * See detailed usage example at the top of this document (adminlib.php)
788  *
789  * @author Vincenzo K. Marcovecchio
790  * @package admin
791  */
792 class admin_externalpage implements part_of_admin_tree {
794     /**
795      * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
796      */
797     public $name;
799     /**
800      * @var string The displayed name for this external page. Usually obtained through get_string().
801      */
802     public $visiblename;
804     /**
805      * @var string The external URL that we should link to when someone requests this external page.
806      */
807     public $url;
809     /**
810      * @var string The role capability/permission a user must have to access this external page.
811      */
812     public $req_capability;
814     /**
815      * @var object The context in which capability/permission should be checked, default is site context.
816      */
817     public $context;
819     /**
820      * @var bool hidden in admin tree block.
821      */
822     public $hidden;
824     /**
825      * visible path
826      */
827     public $path;
828     public $visiblepath;
830     /**
831      * Constructor for adding an external page into the admin tree.
832      *
833      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
834      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
835      * @param string $url The external URL that we should link to when someone requests this external page.
836      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
837      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
838      * @param context $context The context the page relates to. Not sure what happens
839      *      if you specify something other than system or front page. Defaults to system.
840      */
841     public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
842         $this->name        = $name;
843         $this->visiblename = $visiblename;
844         $this->url         = $url;
845         if (is_array($req_capability)) {
846             $this->req_capability = $req_capability;
847         } else {
848             $this->req_capability = array($req_capability);
849         }
850         $this->hidden = $hidden;
851         $this->context = $context;
852     }
854     /**
855      * Returns a reference to the part_of_admin_tree object with internal name $name.
856      *
857      * @param string $name The internal name of the object we want.
858      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
859      */
860     public function locate($name, $findpath=false) {
861         if ($this->name == $name) {
862             if ($findpath) {
863                 $this->visiblepath = array($this->visiblename);
864                 $this->path        = array($this->name);
865             }
866             return $this;
867         } else {
868             $return = NULL;
869             return $return;
870         }
871     }
873     public function prune($name) {
874         return false;
875     }
877     /**
878      * Search using query
879      * @param strin query
880      * @return mixed array-object structure of found settings and pages
881      */
882     public function search($query) {
883         $textlib = textlib_get_instance();
885         $found = false;
886         if (strpos(strtolower($this->name), $query) !== false) {
887             $found = true;
888         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
889             $found = true;
890         }
891         if ($found) {
892             $result = new object();
893             $result->page     = $this;
894             $result->settings = array();
895             return array($this->name => $result);
896         } else {
897             return array();
898         }
899     }
901     /**
902      * Determines if the current user has access to this external page based on $this->req_capability.
903      * @return bool True if user has access, false otherwise.
904      */
905     public function check_access() {
906         global $CFG;
907         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
908         foreach($this->req_capability as $cap) {
909             if (is_valid_capability($cap) and has_capability($cap, $context)) {
910                 return true;
911             }
912         }
913         return false;
914     }
916     /**
917      * Is this external page hidden in admin tree block?
918      *
919      * @return bool True if hidden
920      */
921     public function is_hidden() {
922         return $this->hidden;
923     }
927 /**
928  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
929  *
930  * @author Vincenzo K. Marcovecchio
931  * @package admin
932  */
933 class admin_settingpage implements part_of_admin_tree {
935     /**
936      * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
937      */
938     public $name;
940     /**
941      * @var string The displayed name for this external page. Usually obtained through get_string().
942      */
943     public $visiblename;
944     /**
945      * @var mixed An array of admin_setting objects that are part of this setting page.
946      */
947     public $settings;
949     /**
950      * @var string The role capability/permission a user must have to access this external page.
951      */
952     public $req_capability;
954     /**
955      * @var object The context in which capability/permission should be checked, default is site context.
956      */
957     public $context;
959     /**
960      * @var bool hidden in admin tree block.
961      */
962     public $hidden;
964     /**
965      * paths
966      */
967     public $path;
968     public $visiblepath;
970     // see admin_externalpage
971     public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
972         $this->settings    = new object();
973         $this->name        = $name;
974         $this->visiblename = $visiblename;
975         if (is_array($req_capability)) {
976             $this->req_capability = $req_capability;
977         } else {
978             $this->req_capability = array($req_capability);
979         }
980         $this->hidden      = $hidden;
981         $this->context     = $context;
982     }
984     // see admin_category
985     public function locate($name, $findpath=false) {
986         if ($this->name == $name) {
987             if ($findpath) {
988                 $this->visiblepath = array($this->visiblename);
989                 $this->path        = array($this->name);
990             }
991             return $this;
992         } else {
993             $return = NULL;
994             return $return;
995         }
996     }
998     public function search($query) {
999         $found = array();
1001         foreach ($this->settings as $setting) {
1002             if ($setting->is_related($query)) {
1003                 $found[] = $setting;
1004             }
1005         }
1007         if ($found) {
1008             $result = new object();
1009             $result->page     = $this;
1010             $result->settings = $found;
1011             return array($this->name => $result);
1012         }
1014         $textlib = textlib_get_instance();
1016         $found = false;
1017         if (strpos(strtolower($this->name), $query) !== false) {
1018             $found = true;
1019         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1020             $found = true;
1021         }
1022         if ($found) {
1023             $result = new object();
1024             $result->page     = $this;
1025             $result->settings = array();
1026             return array($this->name => $result);
1027         } else {
1028             return array();
1029         }
1030     }
1032     public function prune($name) {
1033         return false;
1034     }
1036     /**
1037      * 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
1038      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1039      * @param object $setting is the admin_setting object you want to add
1040      * @return true if successful, false if not
1041      */
1042     public function add($setting) {
1043         if (!($setting instanceof admin_setting)) {
1044             debugging('error - not a setting instance');
1045             return false;
1046         }
1048         $this->settings->{$setting->name} = $setting;
1049         return true;
1050     }
1052     // see admin_externalpage
1053     public function check_access() {
1054         global $CFG;
1055         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
1056         foreach($this->req_capability as $cap) {
1057             if (is_valid_capability($cap) and has_capability($cap, $context)) {
1058                 return true;
1059             }
1060         }
1061         return false;
1062     }
1064     /**
1065      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1066      * returns a string of the html
1067      */
1068     public function output_html() {
1069         $adminroot = admin_get_root();
1070         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1071         foreach($this->settings as $setting) {
1072             $fullname = $setting->get_full_name();
1073             if (array_key_exists($fullname, $adminroot->errors)) {
1074                 $data = $adminroot->errors[$fullname]->data;
1075             } else {
1076                 $data = $setting->get_setting();
1077                 // do not use defaults if settings not available - upgrdesettings handles the defaults!
1078             }
1079             $return .= $setting->output_html($data);
1080         }
1081         $return .= '</fieldset>';
1082         return $return;
1083     }
1085     /**
1086      * Is this settigns page hidden in admin tree block?
1087      *
1088      * @return bool True if hidden
1089      */
1090     public function is_hidden() {
1091         return $this->hidden;
1092     }
1097 /**
1098  * Admin settings class. Only exists on setting pages.
1099  * Read & write happens at this level; no authentication.
1100  */
1101 abstract class admin_setting {
1103     public $name;
1104     public $visiblename;
1105     public $description;
1106     public $defaultsetting;
1107     public $updatedcallback;
1108     public $plugin; // null means main config table
1110     /**
1111      * Constructor
1112      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1113      * @param string $visiblename localised name
1114      * @param string $description localised long description
1115      * @param mixed $defaultsetting string or array depending on implementation
1116      */
1117     public function __construct($name, $visiblename, $description, $defaultsetting) {
1118         $this->parse_setting_name($name);
1119         $this->visiblename    = $visiblename;
1120         $this->description    = $description;
1121         $this->defaultsetting = $defaultsetting;
1122     }
1124     /**
1125      * Set up $this->name and possibly $this->plugin based on whether $name looks
1126      * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1127      * on the names, that is, output a developer debug warning if the name
1128      * contains anything other than [a-zA-Z0-9_]+.
1129      *
1130      * @param string $name the setting name passed in to the constructor.
1131      */
1132     private function parse_setting_name($name) {
1133         $bits = explode('/', $name);
1134         if (count($bits) > 2) {
1135             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1136         }
1137         $this->name = array_pop($bits);
1138         if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1139             throw new moodle_exception('invalidadminsettingname', '', '', $name);
1140         }
1141         if (!empty($bits)) {
1142             $this->plugin = array_pop($bits);
1143             if ($this->plugin === 'moodle') {
1144                 $this->plugin = null;
1145             } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1146                 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1147             }
1148         }
1149     }
1151     public function get_full_name() {
1152         return 's_'.$this->plugin.'_'.$this->name;
1153     }
1155     public function get_id() {
1156         return 'id_s_'.$this->plugin.'_'.$this->name;
1157     }
1159     public function config_read($name) {
1160         global $CFG;
1161         if (!empty($this->plugin)) {
1162             $value = get_config($this->plugin, $name);
1163             return $value === false ? NULL : $value;
1165         } else {
1166             if (isset($CFG->$name)) {
1167                 return $CFG->$name;
1168             } else {
1169                 return NULL;
1170             }
1171         }
1172     }
1174     /**
1175      *
1176      * @param <type> $name
1177      * @param <type> $value
1178      * @return <type> Write setting to confix table
1179      */
1180     public function config_write($name, $value) {
1181         global $DB, $USER, $CFG;
1183         // make sure it is a real change
1184         $oldvalue = get_config($this->plugin, $name);
1185         $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1186         $value = is_null($value) ? null : (string)$value;
1188         if ($oldvalue === $value) {
1189             return true;
1190         }
1192         // store change
1193         set_config($name, $value, $this->plugin);
1196         // log change
1197         $log = new object();
1198         $log->userid       = empty($CFG->rolesactive) ? 0 :$USER->id; // 0 as user id during install
1199         $log->timemodified = time();
1200         $log->plugin       = $this->plugin;
1201         $log->name         = $name;
1202         $log->value        = $value;
1203         $log->oldvalue     = $oldvalue;
1204         $DB->insert_record('config_log', $log);
1206         return true; // BC only
1207     }
1209     /**
1210      * Returns current value of this setting
1211      * @return mixed array or string depending on instance, NULL means not set yet
1212      */
1213     public abstract function get_setting();
1215     /**
1216      * Returns default setting if exists
1217      * @return mixed array or string depending on instance; NULL means no default, user must supply
1218      */
1219     public function get_defaultsetting() {
1220         $adminroot =  admin_get_root(false, false);
1221         if (!empty($adminroot->custom_defaults)) {
1222             $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1223             if (isset($adminroot->custom_defaults[$plugin])) {
1224                 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid valie here ;-)
1225                     return $adminroot->custom_defaults[$plugin][$this->name];
1226                 }
1227             }
1228         }
1229         return $this->defaultsetting;
1230     }
1232     /**
1233      * Store new setting
1234      * @param mixed string or array, must not be NULL
1235      * @return '' if ok, string error message otherwise
1236      */
1237     public abstract function write_setting($data);
1239     /**
1240      * Return part of form with setting
1241      * @param mixed data array or string depending on setting
1242      * @return string
1243      */
1244     public function output_html($data, $query='') {
1245         // should be overridden
1246         return;
1247     }
1249     /**
1250      * function called if setting updated - cleanup, cache reset, etc.
1251      */
1252     public function set_updatedcallback($functionname) {
1253         $this->updatedcallback = $functionname;
1254     }
1256     /**
1257      * Is setting related to query text - used when searching
1258      * @param string $query
1259      * @return bool
1260      */
1261     public function is_related($query) {
1262         if (strpos(strtolower($this->name), $query) !== false) {
1263             return true;
1264         }
1265         $textlib = textlib_get_instance();
1266         if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1267             return true;
1268         }
1269         if (strpos($textlib->strtolower($this->description), $query) !== false) {
1270             return true;
1271         }
1272         $current = $this->get_setting();
1273         if (!is_null($current)) {
1274             if (is_string($current)) {
1275                 if (strpos($textlib->strtolower($current), $query) !== false) {
1276                     return true;
1277                 }
1278             }
1279         }
1280         $default = $this->get_defaultsetting();
1281         if (!is_null($default)) {
1282             if (is_string($default)) {
1283                 if (strpos($textlib->strtolower($default), $query) !== false) {
1284                     return true;
1285                 }
1286             }
1287         }
1288         return false;
1289     }
1292 /**
1293  * No setting - just heading and text.
1294  */
1295 class admin_setting_heading extends admin_setting {
1296     /**
1297      * not a setting, just text
1298      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1299      * @param string $heading heading
1300      * @param string $information text in box
1301      */
1302     public function __construct($name, $heading, $information) {
1303         parent::__construct($name, $heading, $information, '');
1304     }
1306     public function get_setting() {
1307         return true;
1308     }
1310     public function get_defaultsetting() {
1311         return true;
1312     }
1314     public function write_setting($data) {
1315         // do not write any setting
1316         return '';
1317     }
1319     public function output_html($data, $query='') {
1320         $return = '';
1321         if ($this->visiblename != '') {
1322             $return .= print_heading('<a name="'.$this->name.'">'.highlightfast($query, $this->visiblename).'</a>', '', 3, 'main', true);
1323         }
1324         if ($this->description != '') {
1325             $return .= print_box(highlight($query, $this->description), 'generalbox formsettingheading', '', true);
1326         }
1327         return $return;
1328     }
1331 /**
1332  * The most flexibly setting, user is typing text
1333  */
1334 class admin_setting_configtext extends admin_setting {
1336     public $paramtype;
1337     public $size;
1339     /**
1340      * config text contructor
1341      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1342      * @param string $visiblename localised
1343      * @param string $description long localised info
1344      * @param string $defaultsetting
1345      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
1346      * @param int $size default field size
1347      */
1348     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
1349         $this->paramtype = $paramtype;
1350         if (!is_null($size)) {
1351             $this->size  = $size;
1352         } else {
1353             $this->size  = ($paramtype == PARAM_INT) ? 5 : 30;
1354         }
1355         parent::__construct($name, $visiblename, $description, $defaultsetting);
1356     }
1358     public function get_setting() {
1359         return $this->config_read($this->name);
1360     }
1362     public function write_setting($data) {
1363         if ($this->paramtype === PARAM_INT and $data === '') {
1364             // do not complain if '' used instead of 0
1365             $data = 0;
1366         }
1367         // $data is a string
1368         $validated = $this->validate($data);
1369         if ($validated !== true) {
1370             return $validated;
1371         }
1372         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1373     }
1375     /**
1376      * Validate data before storage
1377      * @param string data
1378      * @return mixed true if ok string if error found
1379      */
1380     public function validate($data) {
1381         if (is_string($this->paramtype)) {
1382             if (preg_match($this->paramtype, $data)) {
1383                 return true;
1384             } else {
1385                 return get_string('validateerror', 'admin');
1386             }
1388         } else if ($this->paramtype === PARAM_RAW) {
1389             return true;
1391         } else {
1392             $cleaned = clean_param($data, $this->paramtype);
1393             if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
1394                 return true;
1395             } else {
1396                 return get_string('validateerror', 'admin');
1397             }
1398         }
1399     }
1401     public function output_html($data, $query='') {
1402         $default = $this->get_defaultsetting();
1404         return format_admin_setting($this, $this->visiblename,
1405                 '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
1406                 $this->description, true, '', $default, $query);
1407     }
1410 /**
1411  * General text area without html editor.
1412  */
1413 class admin_setting_configtextarea extends admin_setting_configtext {
1414     public $rows;
1415     public $cols;
1417     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1418         $this->rows = $rows;
1419         $this->cols = $cols;
1420         parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
1421     }
1423     public function output_html($data, $query='') {
1424         $default = $this->get_defaultsetting();
1426         $defaultinfo = $default;
1427         if (!is_null($default) and $default !== '') {
1428             $defaultinfo = "\n".$default;
1429         }
1431         return format_admin_setting($this, $this->visiblename,
1432                 '<div class="form-textarea form-textarea-advanced" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1433                 $this->description, true, '', $defaultinfo, $query);
1434     }
1437 /**
1438  * Password field, allows unmasking of password
1439  */
1440 class admin_setting_configpasswordunmask extends admin_setting_configtext {
1441     /**
1442      * Constructor
1443      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1444      * @param string $visiblename localised
1445      * @param string $description long localised info
1446      * @param string $defaultsetting default password
1447      */
1448     public function __construct($name, $visiblename, $description, $defaultsetting) {
1449         parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
1450     }
1452     public function output_html($data, $query='') {
1453         $id = $this->get_id();
1454         $unmask = get_string('unmaskpassword', 'form');
1455         $unmaskjs = '<script type="text/javascript">
1456 //<![CDATA[
1457 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
1459 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
1461 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
1463 var unmaskchb = document.createElement("input");
1464 unmaskchb.setAttribute("type", "checkbox");
1465 unmaskchb.setAttribute("id", "'.$id.'unmask");
1466 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
1467 unmaskdiv.appendChild(unmaskchb);
1469 var unmasklbl = document.createElement("label");
1470 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
1471 if (is_ie) {
1472   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
1473 } else {
1474   unmasklbl.setAttribute("for", "'.$id.'unmask");
1476 unmaskdiv.appendChild(unmasklbl);
1478 if (is_ie) {
1479   // ugly hack to work around the famous onchange IE bug
1480   unmaskchb.onclick = function() {this.blur();};
1481   unmaskdiv.onclick = function() {this.blur();};
1483 //]]>
1484 </script>';
1485         return format_admin_setting($this, $this->visiblename,
1486                 '<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>',
1487                 $this->description, true, '', NULL, $query);
1488     }
1491 /**
1492  * Path to directory
1493  */
1494 class admin_setting_configfile extends admin_setting_configtext {
1495     /**
1496      * Constructor
1497      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1498      * @param string $visiblename localised
1499      * @param string $description long localised info
1500      * @param string $defaultdirectory default directory location
1501      */
1502     public function __construct($name, $visiblename, $description, $defaultdirectory) {
1503         parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
1504     }
1506     public function output_html($data, $query='') {
1507         $default = $this->get_defaultsetting();
1509         if ($data) {
1510             if (file_exists($data)) {
1511                 $executable = '<span class="pathok">&#x2714;</span>';
1512             } else {
1513                 $executable = '<span class="patherror">&#x2718;</span>';
1514             }
1515         } else {
1516             $executable = '';
1517         }
1519         return format_admin_setting($this, $this->visiblename,
1520                 '<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>',
1521                 $this->description, true, '', $default, $query);
1522     }
1525 /**
1526  * Path to executable file
1527  */
1528 class admin_setting_configexecutable extends admin_setting_configfile {
1530     public function output_html($data, $query='') {
1531         $default = $this->get_defaultsetting();
1533         if ($data) {
1534             if (file_exists($data) and is_executable($data)) {
1535                 $executable = '<span class="pathok">&#x2714;</span>';
1536             } else {
1537                 $executable = '<span class="patherror">&#x2718;</span>';
1538             }
1539         } else {
1540             $executable = '';
1541         }
1543         return format_admin_setting($this, $this->visiblename,
1544                 '<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>',
1545                 $this->description, true, '', $default, $query);
1546     }
1549 /**
1550  * Path to directory
1551  */
1552 class admin_setting_configdirectory extends admin_setting_configfile {
1553     public function output_html($data, $query='') {
1554         $default = $this->get_defaultsetting();
1556         if ($data) {
1557             if (file_exists($data) and is_dir($data)) {
1558                 $executable = '<span class="pathok">&#x2714;</span>';
1559             } else {
1560                 $executable = '<span class="patherror">&#x2718;</span>';
1561             }
1562         } else {
1563             $executable = '';
1564         }
1566         return format_admin_setting($this, $this->visiblename,
1567                 '<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>',
1568                 $this->description, true, '', $default, $query);
1569     }
1572 /**
1573  * Checkbox
1574  */
1575 class admin_setting_configcheckbox extends admin_setting {
1576     public $yes;
1577     public $no;
1579     /**
1580      * Constructor
1581      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1582      * @param string $visiblename localised
1583      * @param string $description long localised info
1584      * @param string $defaultsetting
1585      * @param string $yes value used when checked
1586      * @param string $no value used when not checked
1587      */
1588     public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
1589         parent::__construct($name, $visiblename, $description, $defaultsetting);
1590         $this->yes = (string)$yes;
1591         $this->no  = (string)$no;
1592     }
1594     public function get_setting() {
1595         return $this->config_read($this->name);
1596     }
1598     public function write_setting($data) {
1599         if ((string)$data === $this->yes) { // convert to strings before comparison
1600             $data = $this->yes;
1601         } else {
1602             $data = $this->no;
1603         }
1604         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1605     }
1607     public function output_html($data, $query='') {
1608         $default = $this->get_defaultsetting();
1610         if (!is_null($default)) {
1611             if ((string)$default === $this->yes) {
1612                 $defaultinfo = get_string('checkboxyes', 'admin');
1613             } else {
1614                 $defaultinfo = get_string('checkboxno', 'admin');
1615             }
1616         } else {
1617             $defaultinfo = NULL;
1618         }
1620         if ((string)$data === $this->yes) { // convert to strings before comparison
1621             $checked = 'checked="checked"';
1622         } else {
1623             $checked = '';
1624         }
1626         return format_admin_setting($this, $this->visiblename,
1627                 '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
1628                 .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
1629                 $this->description, true, '', $defaultinfo, $query);
1630     }
1633 /**
1634  * Multiple checkboxes, each represents different value, stored in csv format
1635  */
1636 class admin_setting_configmulticheckbox extends admin_setting {
1637     public $choices;
1639     /**
1640      * Constructor
1641      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1642      * @param string $visiblename localised
1643      * @param string $description long localised info
1644      * @param array $defaultsetting array of selected
1645      * @param array $choices array of $value=>$label for each checkbox
1646      */
1647     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
1648         $this->choices = $choices;
1649         parent::__construct($name, $visiblename, $description, $defaultsetting);
1650     }
1652     /**
1653      * This public function may be used in ancestors for lazy loading of choices
1654      * @return true if loaded, false if error
1655      */
1656     public function load_choices() {
1657         /*
1658         if (is_array($this->choices)) {
1659             return true;
1660         }
1661         .... load choices here
1662         */
1663         return true;
1664     }
1666     /**
1667      * Is setting related to query text - used when searching
1668      * @param string $query
1669      * @return bool
1670      */
1671     public function is_related($query) {
1672         if (!$this->load_choices() or empty($this->choices)) {
1673             return false;
1674         }
1675         if (parent::is_related($query)) {
1676             return true;
1677         }
1679         $textlib = textlib_get_instance();
1680         foreach ($this->choices as $desc) {
1681             if (strpos($textlib->strtolower($desc), $query) !== false) {
1682                 return true;
1683             }
1684         }
1685         return false;
1686     }
1688     public function get_setting() {
1689         $result = $this->config_read($this->name);
1691         if (is_null($result)) {
1692             return NULL;
1693         }
1694         if ($result === '') {
1695             return array();
1696         }
1697         $enabled = explode(',', $result);
1698         $setting = array();
1699         foreach ($enabled as $option) {
1700             $setting[$option] = 1;
1701         }
1702         return $setting;
1703     }
1705     public function write_setting($data) {
1706         if (!is_array($data)) {
1707             return ''; // ignore it
1708         }
1709         if (!$this->load_choices() or empty($this->choices)) {
1710             return '';
1711         }
1712         unset($data['xxxxx']);
1713         $result = array();
1714         foreach ($data as $key => $value) {
1715             if ($value and array_key_exists($key, $this->choices)) {
1716                 $result[] = $key;
1717             }
1718         }
1719         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
1720     }
1722     public function output_html($data, $query='') {
1723         if (!$this->load_choices() or empty($this->choices)) {
1724             return '';
1725         }
1726         $default = $this->get_defaultsetting();
1727         if (is_null($default)) {
1728             $default = array();
1729         }
1730         if (is_null($data)) {
1731             $data = array();
1732         }
1733         $options = array();
1734         $defaults = array();
1735         foreach ($this->choices as $key=>$description) {
1736             if (!empty($data[$key])) {
1737                 $checked = 'checked="checked"';
1738             } else {
1739                 $checked = '';
1740             }
1741             if (!empty($default[$key])) {
1742                 $defaults[] = $description;
1743             }
1745             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
1746                          .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
1747         }
1749         if (is_null($default)) {
1750             $defaultinfo = NULL;
1751         } else if (!empty($defaults)) {
1752             $defaultinfo = implode(', ', $defaults);
1753         } else {
1754             $defaultinfo = get_string('none');
1755         }
1757         $return = '<div class="form-multicheckbox">';
1758         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
1759         if ($options) {
1760             $return .= '<ul>';
1761             foreach ($options as $option) {
1762                 $return .= '<li>'.$option.'</li>';
1763             }
1764             $return .= '</ul>';
1765         }
1766         $return .= '</div>';
1768         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
1770     }
1773 /**
1774  * Multiple checkboxes 2, value stored as string 00101011
1775  */
1776 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
1777     public function get_setting() {
1778         $result = $this->config_read($this->name);
1779         if (is_null($result)) {
1780             return NULL;
1781         }
1782         if (!$this->load_choices()) {
1783             return NULL;
1784         }
1785         $result = str_pad($result, count($this->choices), '0');
1786         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
1787         $setting = array();
1788         foreach ($this->choices as $key=>$unused) {
1789             $value = array_shift($result);
1790             if ($value) {
1791                 $setting[$key] = 1;
1792             }
1793         }
1794         return $setting;
1795     }
1797     public function write_setting($data) {
1798         if (!is_array($data)) {
1799             return ''; // ignore it
1800         }
1801         if (!$this->load_choices() or empty($this->choices)) {
1802             return '';
1803         }
1804         $result = '';
1805         foreach ($this->choices as $key=>$unused) {
1806             if (!empty($data[$key])) {
1807                 $result .= '1';
1808             } else {
1809                 $result .= '0';
1810             }
1811         }
1812         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
1813     }
1816 /**
1817  * Select one value from list
1818  */
1819 class admin_setting_configselect extends admin_setting {
1820     public $choices;
1822     /**
1823      * Constructor
1824      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1825      * @param string $visiblename localised
1826      * @param string $description long localised info
1827      * @param string $defaultsetting
1828      * @param array $choices array of $value=>$label for each selection
1829      */
1830     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
1831         $this->choices = $choices;
1832         parent::__construct($name, $visiblename, $description, $defaultsetting);
1833     }
1835     /**
1836      * This function may be used in ancestors for lazy loading of choices
1837      * @return true if loaded, false if error
1838      */
1839     public function load_choices() {
1840         /*
1841         if (is_array($this->choices)) {
1842             return true;
1843         }
1844         .... load choices here
1845         */
1846         return true;
1847     }
1849     public function is_related($query) {
1850         if (parent::is_related($query)) {
1851             return true;
1852         }
1853         if (!$this->load_choices()) {
1854             return false;
1855         }
1856         $textlib = textlib_get_instance();
1857         foreach ($this->choices as $key=>$value) {
1858             if (strpos($textlib->strtolower($key), $query) !== false) {
1859                 return true;
1860             }
1861             if (strpos($textlib->strtolower($value), $query) !== false) {
1862                 return true;
1863             }
1864         }
1865         return false;
1866     }
1868     public function get_setting() {
1869         return $this->config_read($this->name);
1870     }
1872     public function write_setting($data) {
1873         if (!$this->load_choices() or empty($this->choices)) {
1874             return '';
1875         }
1876         if (!array_key_exists($data, $this->choices)) {
1877             return ''; // ignore it
1878         }
1880         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
1881     }
1883     /**
1884      * Ensure the options are loaded, and generate the HTML for the select
1885      * element and any warning message. Separating this out from output_html
1886      * makes it easier to subclass this class.
1887      *
1888      * @param string $data the option to show as selected.
1889      * @param string $current the currently selected option in the database, null if none.
1890      * @param string $default the default selected option.
1891      * @return array the HTML for the select element, and a warning message.
1892      */
1893     public function output_select_html($data, $current, $default, $extraname = '') {
1894         if (!$this->load_choices() or empty($this->choices)) {
1895             return array('', '');
1896         }
1898         $warning = '';
1899         if (is_null($current)) {
1900             // first run
1901         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
1902             // no warning
1903         } else if (!array_key_exists($current, $this->choices)) {
1904             $warning = get_string('warningcurrentsetting', 'admin', s($current));
1905             if (!is_null($default) and $data == $current) {
1906                 $data = $default; // use default instead of first value when showing the form
1907             }
1908         }
1910         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
1911         foreach ($this->choices as $key => $value) {
1912             // the string cast is needed because key may be integer - 0 is equal to most strings!
1913             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
1914         }
1915         $selecthtml .= '</select>';
1916         return array($selecthtml, $warning);
1917     }
1919     public function output_html($data, $query='') {
1920         $default = $this->get_defaultsetting();
1921         $current = $this->get_setting();
1923         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
1924         if (!$selecthtml) {
1925             return '';
1926         }
1928         if (!is_null($default) and array_key_exists($default, $this->choices)) {
1929             $defaultinfo = $this->choices[$default];
1930         } else {
1931             $defaultinfo = NULL;
1932         }
1934         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
1936         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
1937     }
1940 /**
1941  * Select multiple items from list
1942  */
1943 class admin_setting_configmultiselect extends admin_setting_configselect {
1944     /**
1945      * Constructor
1946      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1947      * @param string $visiblename localised
1948      * @param string $description long localised info
1949      * @param array $defaultsetting array of selected items
1950      * @param array $choices array of $value=>$label for each list item
1951      */
1952     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
1953         parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
1954     }
1956     public function get_setting() {
1957         $result = $this->config_read($this->name);
1958         if (is_null($result)) {
1959             return NULL;
1960         }
1961         if ($result === '') {
1962             return array();
1963         }
1964         return explode(',', $result);
1965     }
1967     public function write_setting($data) {
1968         if (!is_array($data)) {
1969             return ''; //ignore it
1970         }
1971         if (!$this->load_choices() or empty($this->choices)) {
1972             return '';
1973         }
1975         unset($data['xxxxx']);
1977         $save = array();
1978         foreach ($data as $value) {
1979             if (!array_key_exists($value, $this->choices)) {
1980                 continue; // ignore it
1981             }
1982             $save[] = $value;
1983         }
1985         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
1986     }
1988     /**
1989      * Is setting related to query text - used when searching
1990      * @param string $query
1991      * @return bool
1992      */
1993     public function is_related($query) {
1994         if (!$this->load_choices() or empty($this->choices)) {
1995             return false;
1996         }
1997         if (parent::is_related($query)) {
1998             return true;
1999         }
2001         $textlib = textlib_get_instance();
2002         foreach ($this->choices as $desc) {
2003             if (strpos($textlib->strtolower($desc), $query) !== false) {
2004                 return true;
2005             }
2006         }
2007         return false;
2008     }
2010     public function output_html($data, $query='') {
2011         if (!$this->load_choices() or empty($this->choices)) {
2012             return '';
2013         }
2014         $choices = $this->choices;
2015         $default = $this->get_defaultsetting();
2016         if (is_null($default)) {
2017             $default = array();
2018         }
2019         if (is_null($data)) {
2020             $data = array();
2021         }
2023         $defaults = array();
2024         $size = min(10, count($this->choices));
2025         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2026         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
2027         foreach ($this->choices as $key => $description) {
2028             if (in_array($key, $data)) {
2029                 $selected = 'selected="selected"';
2030             } else {
2031                 $selected = '';
2032             }
2033             if (in_array($key, $default)) {
2034                 $defaults[] = $description;
2035             }
2037             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2038         }
2040         if (is_null($default)) {
2041             $defaultinfo = NULL;
2042         } if (!empty($defaults)) {
2043             $defaultinfo = implode(', ', $defaults);
2044         } else {
2045             $defaultinfo = get_string('none');
2046         }
2048         $return .= '</select></div>';
2049         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
2050     }
2053 /**
2054  * Time selector
2055  * this is a liiitle bit messy. we're using two selects, but we're returning
2056  * them as an array named after $name (so we only use $name2 internally for the setting)
2057  */
2058 class admin_setting_configtime extends admin_setting {
2059     public $name2;
2061     /**
2062      * Constructor
2063      * @param string $hoursname setting for hours
2064      * @param string $minutesname setting for hours
2065      * @param string $visiblename localised
2066      * @param string $description long localised info
2067      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2068      */
2069     public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
2070         $this->name2 = $minutesname;
2071         parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
2072     }
2074     public function get_setting() {
2075         $result1 = $this->config_read($this->name);
2076         $result2 = $this->config_read($this->name2);
2077         if (is_null($result1) or is_null($result2)) {
2078             return NULL;
2079         }
2081         return array('h' => $result1, 'm' => $result2);
2082     }
2084     public function write_setting($data) {
2085         if (!is_array($data)) {
2086             return '';
2087         }
2089         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2090         return ($result ? '' : get_string('errorsetting', 'admin'));
2091     }
2093     public function output_html($data, $query='') {
2094         $default = $this->get_defaultsetting();
2096         if (is_array($default)) {
2097             $defaultinfo = $default['h'].':'.$default['m'];
2098         } else {
2099             $defaultinfo = NULL;
2100         }
2102         $return = '<div class="form-time defaultsnext">'.
2103                   '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
2104         for ($i = 0; $i < 24; $i++) {
2105             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2106         }
2107         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2108         for ($i = 0; $i < 60; $i += 5) {
2109             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2110         }
2111         $return .= '</select></div>';
2112         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2113     }
2117 class admin_setting_configiplist extends admin_setting_configtextarea {
2118     public function validate($data) {
2119         if(!empty($data)) {
2120             $ips = explode("\n", $data);
2121         } else {
2122             return true;
2123         }
2124         $result = true;
2125         foreach($ips as $ip) {
2126             $ip = trim($ip);
2127             if(preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
2128                    preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
2129                    preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
2130                 $result = true;
2131             } else {
2132                 $result = false;
2133                 break;
2134             }
2135         }
2136         if($result){
2137             return true;
2138         } else {
2139             return get_string('validateerror', 'admin');
2140         }
2141     }
2144 /**
2145  * An admin setting for selecting one or more users, who have a particular capability
2146  * in the system context. Warning, make sure the list will never be too long. There is
2147  * no paging or searching of this list.
2148  *
2149  * To correctly get a list of users from this config setting, you need to call the
2150  * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
2151  */
2152 class admin_setting_users_with_capability extends admin_setting_configmultiselect {
2153     protected $capability;
2155     /**
2156      * Constructor.
2157      *
2158      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2159      * @param string $visiblename localised name
2160      * @param string $description localised long description
2161      * @param array $defaultsetting array of usernames
2162      * @param string $capability string capability name.
2163      */
2164     function __construct($name, $visiblename, $description, $defaultsetting, $capability) {
2165         $this->capability = $capability;
2166         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
2167     }
2169     function load_choices() {
2170         if (is_array($this->choices)) {
2171             return true;
2172         }
2173         $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM),
2174                 $this->capability, 'u.id,u.username,u.firstname,u.lastname', 'u.lastname,u.firstname');
2175         $this->choices = array(
2176             '$@NONE@$' => get_string('nobody'),
2177             '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
2178         );
2179         foreach ($users as $user) {
2180             $this->choices[$user->username] = fullname($user);
2181         }
2182         return true;
2183     }
2185     public function get_defaultsetting() {
2186         $this->load_choices();
2187         $defaultsetting = parent::get_defaultsetting();
2188         if (empty($defaultsetting)) {
2189             return array('$@NONE@$');
2190         } else if (array_key_exists($defaultsetting, $this->choices)) {
2191             return $defaultsetting;
2192         } else {
2193             return '';
2194         }
2195     }
2197     public function get_setting() {
2198         $result = parent::get_setting();
2199         if (empty($result)) {
2200             $result = array('$@NONE@$');
2201         }
2202         return $result;
2203     }
2205     public function write_setting($data) {
2206         // If all is selected, remove any explicit options.
2207         if (in_array('$@ALL@$', $data)) {
2208             $data = array('$@ALL@$');
2209         }
2210         // None never needs to be writted to the DB.
2211         if (in_array('$@NONE@$', $data)) {
2212             unset($data[array_search('$@NONE@$', $data)]);
2213         }
2214         return parent::write_setting($data);
2215     }
2218 /**
2219  * Special checkbox for calendar - resets SESSION vars.
2220  */
2221 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
2222     public function __construct() {
2223         parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
2224                             get_string('helpadminseesall', 'admin'), '0');
2225     }
2227     public function write_setting($data) {
2228         global $SESSION;
2229         unset($SESSION->cal_courses_shown);
2230         return parent::write_setting($data);
2231     }
2234 /**
2235  * Special select for settings that are altered in setup.php and can not be altered on the fly
2236  */
2237 class admin_setting_special_selectsetup extends admin_setting_configselect {
2238     public function get_setting() {
2239         // read directly from db!
2240         return get_config(NULL, $this->name);
2241     }
2243     public function write_setting($data) {
2244         global $CFG;
2245         // do not change active CFG setting!
2246         $current = $CFG->{$this->name};
2247         $result = parent::write_setting($data);
2248         $CFG->{$this->name} = $current;
2249         return $result;
2250     }
2253 /**
2254  * Special select for frontpage - stores data in course table
2255  */
2256 class admin_setting_sitesetselect extends admin_setting_configselect {
2257     public function get_setting() {
2258         $site = get_site();
2259         return $site->{$this->name};
2260     }
2262     public function write_setting($data) {
2263         global $DB, $SITE;
2264         if (!in_array($data, array_keys($this->choices))) {
2265             return get_string('errorsetting', 'admin');
2266         }
2267         $record = new stdClass();
2268         $record->id           = SITEID;
2269         $temp                 = $this->name;
2270         $record->$temp        = $data;
2271         $record->timemodified = time();
2272         // update $SITE
2273         $SITE->{$this->name} = $data;
2274         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
2275     }
2278 /**
2279  * Special select - lists on the frontpage - hacky
2280  */
2281 class admin_setting_courselist_frontpage extends admin_setting {
2282     public $choices;
2284     public function __construct($loggedin) {
2285         global $CFG;
2286         require_once($CFG->dirroot.'/course/lib.php');
2287         $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
2288         $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
2289         $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
2290         $defaults    = array(FRONTPAGECOURSELIST);
2291         parent::__construct($name, $visiblename, $description, $defaults);
2292     }
2294     public function load_choices() {
2295         global $DB;
2296         if (is_array($this->choices)) {
2297             return true;
2298         }
2299         $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
2300                                FRONTPAGECOURSELIST    => get_string('frontpagecourselist'),
2301                                FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
2302                                FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
2303                                'none'                 => get_string('none'));
2304         if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
2305             unset($this->choices[FRONTPAGECOURSELIST]);
2306         }
2307         return true;
2308     }
2309     public function get_setting() {
2310         $result = $this->config_read($this->name);
2311         if (is_null($result)) {
2312             return NULL;
2313         }
2314         if ($result === '') {
2315             return array();
2316         }
2317         return explode(',', $result);
2318     }
2320     public function write_setting($data) {
2321         if (!is_array($data)) {
2322             return '';
2323         }
2324         $this->load_choices();
2325         $save = array();
2326         foreach($data as $datum) {
2327             if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
2328                 continue;
2329             }
2330             $save[$datum] = $datum; // no duplicates
2331         }
2332         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2333     }
2335     public function output_html($data, $query='') {
2336         $this->load_choices();
2337         $currentsetting = array();
2338         foreach ($data as $key) {
2339             if ($key != 'none' and array_key_exists($key, $this->choices)) {
2340                 $currentsetting[] = $key; // already selected first
2341             }
2342         }
2344         $return = '<div class="form-group">';
2345         for ($i = 0; $i < count($this->choices) - 1; $i++) {
2346             if (!array_key_exists($i, $currentsetting)) {
2347                 $currentsetting[$i] = 'none'; //none
2348             }
2349             $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
2350             foreach ($this->choices as $key => $value) {
2351                 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
2352             }
2353             $return .= '</select>';
2354             if ($i !== count($this->choices) - 2) {
2355                 $return .= '<br />';
2356             }
2357         }
2358         $return .= '</div>';
2360         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
2361     }
2364 /**
2365  * Special checkbox for frontpage - stores data in course table
2366  */
2367 class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
2368     public function get_setting() {
2369         $site = get_site();
2370         return $site->{$this->name};
2371     }
2373     public function write_setting($data) {
2374         global $DB, $SITE;
2375         $record = new object();
2376         $record->id            = SITEID;
2377         $record->{$this->name} = ($data == '1' ? 1 : 0);
2378         $record->timemodified  = time();
2379         // update $SITE
2380         $SITE->{$this->name} = $data;
2381         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
2382     }
2385 /**
2386  * Special text for frontpage - stores data in course table.
2387  * Empty string means not set here. Manual setting is required.
2388  */
2389 class admin_setting_sitesettext extends admin_setting_configtext {
2390     public function get_setting() {
2391         $site = get_site();
2392         return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
2393     }
2395     public function validate($data) {
2396         $cleaned = clean_param($data, PARAM_MULTILANG);
2397         if ($cleaned === '') {
2398             return get_string('required');
2399         }
2400         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
2401             return true;
2402         } else {
2403             return get_string('validateerror', 'admin');
2404         }
2405     }
2407     public function write_setting($data) {
2408         global $DB, $SITE;
2409         $data = trim($data);
2410         $validated = $this->validate($data);
2411         if ($validated !== true) {
2412             return $validated;
2413         }
2415         $record = new object();
2416         $record->id            = SITEID;
2417         $record->{$this->name} = $data;
2418         $record->timemodified  = time();
2419         // update $SITE
2420         $SITE->{$this->name} = $data;
2421         return ($DB->update_record('course', $record) ? '' : get_string('dbupdatefailed', 'error'));
2422     }
2425 /**
2426  * Special text editor for site description.
2427  */
2428 class admin_setting_special_frontpagedesc extends admin_setting {
2429     public function __construct() {
2430         parent::__construct('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
2431     }
2433     public function get_setting() {
2434         $site = get_site();
2435         return $site->{$this->name};
2436     }
2438     public function write_setting($data) {
2439         global $DB, $SITE;
2440         $record = new object();
2441         $record->id            = SITEID;
2442         $record->{$this->name} = $data;
2443         $record->timemodified  = time();
2444         $SITE->{$this->name} = $data;
2445         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
2446     }
2448     public function output_html($data, $query='') {
2449         global $CFG;
2451         $CFG->adminusehtmleditor = can_use_html_editor();
2452         $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
2454         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
2455     }
2458 class admin_setting_special_editorfontlist extends admin_setting {
2460     public $items;
2462     public function __construct() {
2463         global $CFG;
2464         $name = 'editorfontlist';
2465         $visiblename = get_string('editorfontlist', 'admin');
2466         $description = get_string('configeditorfontlist', 'admin');
2467         $defaults = array('k0' => 'Trebuchet',
2468                           'v0' => 'Trebuchet MS,Verdana,Arial,Helvetica,sans-serif',
2469                           'k1' => 'Arial',
2470                           'v1' => 'arial,helvetica,sans-serif',
2471                           'k2' => 'Courier New',
2472                           'v2' => 'courier new,courier,monospace',
2473                           'k3' => 'Georgia',
2474                           'v3' => 'georgia,times new roman,times,serif',
2475                           'k4' => 'Tahoma',
2476                           'v4' => 'tahoma,arial,helvetica,sans-serif',
2477                           'k5' => 'Times New Roman',
2478                           'v5' => 'times new roman,times,serif',
2479                           'k6' => 'Verdana',
2480                           'v6' => 'verdana,arial,helvetica,sans-serif',
2481                           'k7' => 'Impact',
2482                           'v7' => 'impact',
2483                           'k8' => 'Wingdings',
2484                           'v8' => 'wingdings');
2485         parent::__construct($name, $visiblename, $description, $defaults);
2486     }
2488     public function get_setting() {
2489         global $CFG;
2490         $result = $this->config_read($this->name);
2491         if (is_null($result)) {
2492             return NULL;
2493         }
2494         $i = 0;
2495         $currentsetting = array();
2496         $items = explode(';', $result);
2497         foreach ($items as $item) {
2498           $item = explode(':', $item);
2499           $currentsetting['k'.$i] = $item[0];
2500           $currentsetting['v'.$i] = $item[1];
2501           $i++;
2502         }
2503         return $currentsetting;
2504     }
2506     public function write_setting($data) {
2508         // there miiight be an easier way to do this :)
2509         // if this is changed, make sure the $defaults array above is modified so that this
2510         // function processes it correctly
2512         $keys = array();
2513         $values = array();
2515         foreach ($data as $key => $value) {
2516             if (substr($key,0,1) == 'k') {
2517                 $keys[substr($key,1)] = $value;
2518             } elseif (substr($key,0,1) == 'v') {
2519                 $values[substr($key,1)] = $value;
2520             }
2521         }
2523         $result = array();
2524         for ($i = 0; $i < count($keys); $i++) {
2525             if (($keys[$i] !== '') && ($values[$i] !== '')) {
2526                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).':'.clean_param($values[$i], PARAM_NOTAGS);
2527             }
2528         }
2530         return ($this->config_write($this->name, implode(';', $result)) ? '' : get_string('errorsetting', 'admin'));
2531     }
2533     public function output_html($data, $query='') {
2534         $fullname = $this->get_full_name();
2535         $return = '<div class="form-group">';
2536         for ($i = 0; $i < count($data) / 2; $i++) {
2537             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
2538             $return .= '&nbsp;&nbsp;';
2539             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
2540         }
2541         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
2542         $return .= '&nbsp;&nbsp;';
2543         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
2544         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
2545         $return .= '&nbsp;&nbsp;';
2546         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
2547         $return .= '</div>';
2549         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
2550     }
2554 class admin_setting_emoticons extends admin_setting {
2556     public $items;
2558     public function __construct() {
2559         global $CFG;
2560         $name = 'emoticons';
2561         $visiblename = get_string('emoticons', 'admin');
2562         $description = get_string('configemoticons', 'admin');
2563         $defaults = array('k0' => ':-)',
2564                           'v0' => 'smiley',
2565                           'k1' => ':)',
2566                           'v1' => 'smiley',
2567                           'k2' => ':-D',
2568                           'v2' => 'biggrin',
2569                           'k3' => ';-)',
2570                           'v3' => 'wink',
2571                           'k4' => ':-/',
2572                           'v4' => 'mixed',
2573                           'k5' => 'V-.',
2574                           'v5' => 'thoughtful',
2575                           'k6' => ':-P',
2576                           'v6' => 'tongueout',
2577                           'k7' => 'B-)',
2578                           'v7' => 'cool',
2579                           'k8' => '^-)',
2580                           'v8' => 'approve',
2581                           'k9' => '8-)',
2582                           'v9' => 'wideeyes',
2583                           'k10' => ':o)',
2584                           'v10' => 'clown',
2585                           'k11' => ':-(',
2586                           'v11' => 'sad',
2587                           'k12' => ':(',
2588                           'v12' => 'sad',
2589                           'k13' => '8-.',
2590                           'v13' => 'shy',
2591                           'k14' => ':-I',
2592                           'v14' => 'blush',
2593                           'k15' => ':-X',
2594                           'v15' => 'kiss',
2595                           'k16' => '8-o',
2596                           'v16' => 'surprise',
2597                           'k17' => 'P-|',
2598                           'v17' => 'blackeye',
2599                           'k18' => '8-[',
2600                           'v18' => 'angry',
2601                           'k19' => 'xx-P',
2602                           'v19' => 'dead',
2603                           'k20' => '|-.',
2604                           'v20' => 'sleepy',
2605                           'k21' => '}-]',
2606                           'v21' => 'evil',
2607                           'k22' => '(h)',
2608                           'v22' => 'heart',
2609                           'k23' => '(heart)',
2610                           'v23' => 'heart',
2611                           'k24' => '(y)',
2612                           'v24' => 'yes',
2613                           'k25' => '(n)',
2614                           'v25' => 'no',
2615                           'k26' => '(martin)',
2616                           'v26' => 'martin',
2617                           'k27' => '( )',
2618                           'v27' => 'egg');
2619         parent::__construct($name, $visiblename, $description, $defaults);
2620     }
2622     public function get_setting() {
2623         global $CFG;
2624         $result = $this->config_read($this->name);
2625         if (is_null($result)) {
2626             return NULL;
2627         }
2628         $i = 0;
2629         $currentsetting = array();
2630         $items = explode('{;}', $result);
2631         foreach ($items as $item) {
2632           $item = explode('{:}', $item);
2633           $currentsetting['k'.$i] = $item[0];
2634           $currentsetting['v'.$i] = $item[1];
2635           $i++;
2636         }
2637         return $currentsetting;
2638     }
2640     public function write_setting($data) {
2642         // there miiight be an easier way to do this :)
2643         // if this is changed, make sure the $defaults array above is modified so that this
2644         // function processes it correctly
2646         $keys = array();
2647         $values = array();
2649         foreach ($data as $key => $value) {
2650             if (substr($key,0,1) == 'k') {
2651                 $keys[substr($key,1)] = $value;
2652             } elseif (substr($key,0,1) == 'v') {
2653                 $values[substr($key,1)] = $value;
2654             }
2655         }
2657         $result = array();
2658         for ($i = 0; $i < count($keys); $i++) {
2659             if (($keys[$i] !== '') && ($values[$i] !== '')) {
2660                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).'{:}'.clean_param($values[$i], PARAM_NOTAGS);
2661             }
2662         }
2664         return ($this->config_write($this->name, implode('{;}', $result)) ? '' : get_string('errorsetting', 'admin').$this->visiblename.'<br />');
2665     }
2667     public function output_html($data, $query='') {
2668         $fullname = $this->get_full_name();
2669         $return = '<div class="form-group">';
2670         for ($i = 0; $i < count($data) / 2; $i++) {
2671             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
2672             $return .= '&nbsp;&nbsp;';
2673             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
2674         }
2675         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
2676         $return .= '&nbsp;&nbsp;';
2677         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
2678         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
2679         $return .= '&nbsp;&nbsp;';
2680         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
2681         $return .= '</div>';
2683         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
2684     }
2688 class admin_setting_special_editorhidebuttons extends admin_setting {
2689     public $items;
2691     public function __construct() {
2692         parent::__construct('editorhidebuttons', get_string('editorhidebuttons', 'admin'),
2693                             get_string('confeditorhidebuttons', 'admin'), array());
2694         // weird array... buttonname => buttonimage (assume proper path appended). if you leave buttomimage blank, text will be printed instead
2695         $this->items = array('fontname' => '',
2696                          'fontsize' => '',
2697                          'formatblock' => '',
2698                          'bold' => 'ed_format_bold.gif',
2699                          'italic' => 'ed_format_italic.gif',
2700                          'underline' => 'ed_format_underline.gif',
2701                          'strikethrough' => 'ed_format_strike.gif',
2702                          'subscript' => 'ed_format_sub.gif',
2703                          'superscript' => 'ed_format_sup.gif',
2704                          'copy' => 'ed_copy.gif',
2705                          'cut' => 'ed_cut.gif',
2706                          'paste' => 'ed_paste.gif',
2707                          'clean' => 'ed_wordclean.gif',
2708                          'undo' => 'ed_undo.gif',
2709                          'redo' => 'ed_redo.gif',
2710                          'justifyleft' => 'ed_align_left.gif',
2711                          'justifycenter' => 'ed_align_center.gif',
2712                          'justifyright' => 'ed_align_right.gif',
2713                          'justifyfull' => 'ed_align_justify.gif',
2714                          'lefttoright' => 'ed_left_to_right.gif',
2715                          'righttoleft' => 'ed_right_to_left.gif',
2716                          'insertorderedlist' => 'ed_list_num.gif',
2717                          'insertunorderedlist' => 'ed_list_bullet.gif',
2718                          'outdent' => 'ed_indent_less.gif',
2719                          'indent' => 'ed_indent_more.gif',
2720                          'forecolor' => 'ed_color_fg.gif',
2721                          'hilitecolor' => 'ed_color_bg.gif',
2722                          'inserthorizontalrule' => 'ed_hr.gif',
2723                          'createanchor' => 'ed_anchor.gif',
2724                          'createlink' => 'ed_link.gif',
2725                          'unlink' => 'ed_unlink.gif',
2726                          'insertimage' => 'ed_image.gif',
2727                          'inserttable' => 'insert_table.gif',
2728                          'insertsmile' => 'em.icon.smile.gif',
2729                          'insertchar' => 'icon_ins_char.gif',
2730                          'spellcheck' => 'spell-check.gif',
2731                          'htmlmode' => 'ed_html.gif',
2732                          'popupeditor' => 'fullscreen_maximize.gif',
2733                          'search_replace' => 'ed_replace.gif');
2734     }
2736     public function get_setting() {
2737         $result = $this->config_read($this->name);
2738         if (is_null($result)) {
2739             return NULL;
2740         }
2741         if ($result === '') {
2742             return array();
2743         }
2744         return explode(' ', $result);
2745     }
2747     public function write_setting($data) {
2748         if (!is_array($data)) {
2749             return ''; // ignore it
2750         }
2751         unset($data['xxxxx']);
2752         $result = array();
2754         foreach ($data as $key => $value) {
2755             if (!isset($this->items[$key])) {
2756                 return get_string('errorsetting', 'admin');
2757             }
2758             if ($value == '1') {
2759                 $result[] = $key;
2760             }
2761         }
2762         return ($this->config_write($this->name, implode(' ', $result)) ? '' : get_string('errorsetting', 'admin'));
2763     }
2765     public function output_html($data, $query='') {
2767         global $CFG;
2769         // checkboxes with input name="$this->name[$key]" value="1"
2770         // we do 15 fields per column
2772         $return = '<div class="form-group">';
2773         $return .= '<table><tr><td valign="top" align="right">';
2774         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2776         $count = 0;
2778         foreach($this->items as $key => $value) {
2779             if ($count % 15 == 0 and $count != 0) {
2780                 $return .= '</td><td valign="top" align="right">';
2781             }
2783             $return .= '<label for="'.$this->get_id().$key.'">';
2784             $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;';
2785             $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;';
2786             $return .= '</label>';
2787             $count++;
2788             if ($count % 15 != 0) {
2789                 $return .= '<br /><br />';
2790             }
2791         }
2793         $return .= '</td></tr>';
2794         $return .= '</table>';
2795         $return .= '</div>';
2797         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
2798     }
2801 /**
2802  * Special setting for limiting of the list of available languages.
2803  */
2804 class admin_setting_langlist extends admin_setting_configtext {
2805     public function __construct() {
2806         parent::__construct('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
2807     }
2809     public function write_setting($data) {
2810         $return = parent::write_setting($data);
2811         get_list_of_languages(true);//refresh the list
2812         return $return;
2813     }
2816 /**
2817  * Course category selection
2818  */
2819 class admin_settings_coursecat_select extends admin_setting_configselect {
2820     public function __construct($name, $visiblename, $description, $defaultsetting) {
2821         parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
2822     }
2824     public function load_choices() {
2825         global $CFG;
2826         require_once($CFG->dirroot.'/course/lib.php');
2827         if (is_array($this->choices)) {
2828             return true;
2829         }
2830         $this->choices = make_categories_options();
2831         return true;
2832     }
2835 class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
2836     public function __construct() {
2837         parent::__construct('backup_sche_weekdays', get_string('schedule'), get_string('backupschedulehelp'), array(), NULL);
2838         $this->plugin = 'backup';
2839     }
2841     public function load_choices() {
2842         if (is_array($this->choices)) {
2843             return true;
2844         }
2845         $this->choices = array();
2846         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
2847         foreach ($days as $day) {
2848             $this->choices[$day] = get_string($day, 'calendar');
2849         }
2850         return true;
2851     }
2854 /**
2855  * Special debug setting
2856  */
2857 class admin_setting_special_debug extends admin_setting_configselect {
2858     public function __construct() {
2859         parent::__construct('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
2860     }
2862     public function load_choices() {
2863         if (is_array($this->choices)) {
2864             return true;
2865         }
2866         $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
2867                                DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
2868                                DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
2869                                DEBUG_ALL       => get_string('debugall', 'admin'),
2870                                DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
2871         return true;
2872     }
2876 class admin_setting_special_calendar_weekend extends admin_setting {
2877     public function __construct() {
2878         $name = 'calendar_weekend';
2879         $visiblename = get_string('calendar_weekend', 'admin');
2880         $description = get_string('helpweekenddays', 'admin');
2881         $default = array ('0', '6'); // Saturdays and Sundays
2882         parent::__construct($name, $visiblename, $description, $default);
2883     }
2885     public function get_setting() {
2886         $result = $this->config_read($this->name);
2887         if (is_null($result)) {
2888             return NULL;
2889         }
2890         if ($result === '') {
2891             return array();
2892         }
2893         $settings = array();
2894         for ($i=0; $i<7; $i++) {
2895             if ($result & (1 << $i)) {
2896                 $settings[] = $i;
2897             }
2898         }
2899         return $settings;
2900     }
2902     public function write_setting($data) {
2903         if (!is_array($data)) {
2904             return '';
2905         }
2906         unset($data['xxxxx']);
2907         $result = 0;
2908         foreach($data as $index) {
2909             $result |= 1 << $index;
2910         }
2911         return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
2912     }
2914     public function output_html($data, $query='') {
2915         // The order matters very much because of the implied numeric keys
2916         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
2917         $return = '<table><thead><tr>';
2918         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2919         foreach($days as $index => $day) {
2920             $return .= '<td><label for="'.$this->get_id().$index.'">'.get_string($day, 'calendar').'</label></td>';
2921         }
2922         $return .= '</tr></thead><tbody><tr>';
2923         foreach($days as $index => $day) {
2924             $return .= '<td><input type="checkbox" class="form-checkbox" id="'.$this->get_id().$index.'" name="'.$this->get_full_name().'[]" value="'.$index.'" '.(in_array("$index", $data) ? 'checked="checked"' : '').' /></td>';
2925         }
2926         $return .= '</tr></tbody></table>';
2928         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
2930     }
2934 /**
2935  * Admin setting that allows a user to pick appropriate roles for something.
2936  */
2937 class admin_setting_pickroles extends admin_setting_configmulticheckbox {
2938     private $types;
2940     /**
2941      * @param string $name Name of config variable
2942      * @param string $visiblename Display name
2943      * @param string $description Description
2944      * @param array $types Array of capabilities (usually moodle/legacy:something)
2945      *   which identify roles that will be enabled by default. Default is the
2946      *   student role
2947      */
2948     public function __construct($name, $visiblename, $description, $types) {
2949         parent::__construct($name, $visiblename, $description, NULL, NULL);
2950         $this->types = $types;
2951     }
2953     public function load_choices() {
2954         global $CFG, $DB;
2955         if (empty($CFG->rolesactive)) {
2956             return false;
2957         }
2958         if (is_array($this->choices)) {
2959             return true;
2960         }
2961         if ($roles = get_all_roles()) {
2962             $this->choices = array();
2963             foreach($roles as $role) {
2964                 $this->choices[$role->id] = format_string($role->name);
2965             }
2966             return true;
2967         } else {
2968             return false;
2969         }
2970     }
2972     public function get_defaultsetting() {
2973         global $CFG;
2975         if (empty($CFG->rolesactive)) {
2976             return null;
2977         }
2978         $result = array();
2979         foreach($this->types as $capability) {
2980             if ($caproles = get_roles_with_capability($capability, CAP_ALLOW)) {
2981                 foreach ($caproles as $caprole) {
2982                     $result[$caprole->id] = 1;
2983                 }
2984             }
2985         }
2986         return $result;
2987     }
2990 /**
2991  * Text field with an advanced checkbox, that controls a additional "fix_$name" setting.
2992  */
2993 class admin_setting_text_with_advanced extends admin_setting_configtext {
2994     public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype) {
2995         parent::__construct($name, $visiblename, $description,
2996                 $defaultsetting, $paramtype);
2997     }
2999     public function get_setting() {
3000         $value = parent::get_setting();
3001         $fix = $this->config_read('fix_' . $this->name);
3002         if (is_null($value) or is_null($fix)) {
3003             return NULL;
3004         }
3005         return array('value' => $value, 'fix' => $fix);
3006     }
3008     public function write_setting($data) {
3009         $error = parent::write_setting($data['value']);
3010         if (!$error) {
3011             if (empty($data['fix'])) {
3012                 $ok = $this->config_write('fix_' . $this->name, 0);
3013             } else {
3014                 $ok = $this->config_write('fix_' . $this->name, 1);
3015             }
3016             if (!$ok) {
3017                 $error = get_string('errorsetting', 'admin');
3018             }
3019         }
3020         return $error;
3021     }
3023     public function output_html($data, $query='') {
3024         $default = $this->get_defaultsetting();
3025         $defaultinfo = array();
3026         if (isset($default['value'])) {
3027             if ($default['value'] === '') {
3028                 $defaultinfo[] = "''";
3029             } else {
3030                 $defaultinfo[] = $default['value'];
3031             }
3032         }
3033         if (!empty($default['fix'])) {
3034             $defaultinfo[] = get_string('advanced');
3035         }
3036         $defaultinfo = implode(', ', $defaultinfo);
3038         $fix = !empty($data['fix']);
3039         $return = '<div class="form-text defaultsnext">' .
3040                 '<input type="text" size="' . $this->size . '" id="' . $this->get_id() .
3041                 '" name="' . $this->get_full_name() . '[value]" value="' . s($data['value']) . '" />' .
3042                 ' <input type="checkbox" class="form-checkbox" id="' .
3043                 $this->get_id() . '_fix" name="' . $this->get_full_name() .
3044                 '[fix]" value="1" ' . ($fix ? 'checked="checked"' : '') . ' />' .
3045                 ' <label for="' . $this->get_id() . '_fix">' .
3046                 get_string('advanced') . '</label></div>';
3048         return format_admin_setting($this, $this->visiblename, $return,
3049                 $this->description, true, '', $defaultinfo, $query);
3050     }
3053 /**
3054  * Dropdown menu with an advanced checkbox, that controls a additional "fix_$name" setting.
3055  */
3056 class admin_setting_combo_with_advanced extends admin_setting_configselect {
3057     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3058         parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
3059     }
3061     public function get_setting() {
3062         $value = parent::get_setting();
3063         $fix = $this->config_read('fix_' . $this->name);
3064         if (is_null($value) or is_null($fix)) {
3065             return NULL;
3066         }
3067         return array('value' => $value, 'fix' => $fix);
3068     }
3070     public function write_setting($data) {
3071         $error = parent::write_setting($data['value']);
3072         if (!$error) {
3073             if (empty($data['fix'])) {
3074                 $ok = $this->config_write('fix_' . $this->name, 0);
3075             } else {
3076                 $ok = $this->config_write('fix_' . $this->name, 1);
3077             }
3078             if (!$ok) {
3079                 $error = get_string('errorsetting', 'admin');
3080             }
3081         }
3082         return $error;
3083     }
3085     public function output_html($data, $query='') {
3086         $default = $this->get_defaultsetting();
3087         $current = $this->get_setting();
3089         list($selecthtml, $warning) = $this->output_select_html($data['value'],
3090                 $current['value'], $default['value'], '[value]');
3091         if (!$selecthtml) {
3092             return '';
3093         }
3095         if (!is_null($default) and array_key_exists($default['value'], $this->choices)) {
3096             $defaultinfo = array();
3097             if (isset($this->choices[$default['value']])) {
3098                 $defaultinfo[] = $this->choices[$default['value']];
3099             }
3100             if (!empty($default['fix'])) {
3101                 $defaultinfo[] = get_string('advanced');
3102             }
3103             $defaultinfo = implode(', ', $defaultinfo);
3104         } else {
3105             $defaultinfo = '';
3106         }
3108         $fix = !empty($data['fix']);
3109         $return = '<div class="form-select defaultsnext">' . $selecthtml .
3110                 ' <input type="checkbox" class="form-checkbox" id="' .
3111                 $this->get_id() . '_fix" name="' . $this->get_full_name() .
3112                 '[fix]" value="1" ' . ($fix ? 'checked="checked"' : '') . ' />' .
3113                 ' <label for="' . $this->get_id() . '_fix">' .
3114                 get_string('advanced') . '</label></div>';
3116         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
3117     }
3120 /**
3121  * Specialisation of admin_setting_combo_with_advanced for easy yes/no choices.
3122  */
3123 class admin_setting_yesno_with_advanced extends admin_setting_combo_with_advanced {
3124     public function __construct($name, $visiblename, $description, $defaultsetting) {
3125         parent::__construct($name, $visiblename, $description,
3126                 $defaultsetting, array(get_string('no'), get_string('yes')));
3127     }
3130 /**
3131  * Graded roles in gradebook
3132  */
3133 class admin_setting_special_gradebookroles extends admin_setting_pickroles {
3134     public function __construct() {
3135         parent::__construct('gradebookroles', get_string('gradebookroles', 'admin'),
3136                                               get_string('configgradebookroles', 'admin'),
3137                                               array('moodle/legacy:student'));
3138     }
3142 class admin_setting_regradingcheckbox extends admin_setting_configcheckbox {
3143     public function write_setting($data) {
3144         global $CFG, $DB;
3146         $oldvalue  = $this->config_read($this->name);
3147         $return    = parent::write_setting($data);
3148         $newvalue  = $this->config_read($this->name);
3150         if ($oldvalue !== $newvalue) {
3151             // force full regrading
3152             $DB->set_field('grade_items', 'needsupdate', 1, array('needsupdate'=>0));
3153         }
3155         return $return;
3156     }
3159 /**
3160  * Which roles to show on course decription page
3161  */
3162 class admin_setting_special_coursemanager extends admin_setting_pickroles {
3163     public function __construct() {
3164         parent::__construct('coursemanager', get_string('coursemanager', 'admin'),
3165                                              get_string('configcoursemanager', 'admin'),
3166                                              array('moodle/legacy:editingteacher'));
3167     }
3170 class admin_setting_special_gradelimiting extends admin_setting_configcheckbox {
3171     function admin_setting_special_gradelimiting() {
3172         parent::__construct('unlimitedgrades', get_string('unlimitedgrades', 'grades'),
3173                                                   get_string('configunlimitedgrades', 'grades'), '0', '1', '0');
3174     }
3176     function regrade_all() {
3177         global $CFG;
3178         require_once("$CFG->libdir/gradelib.php");
3179         grade_force_site_regrading();
3180     }
3182     function write_setting($data) {
3183         $previous = $this->get_setting();
3185         if ($previous === null) {
3186             if ($data) {
3187                 $this->regrade_all();
3188             }
3189         } else {
3190             if ($data != $previous) {
3191                 $this->regrade_all();
3192             }
3193         }
3194         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3195     }
3199 /**
3200  * Primary grade export plugin - has state tracking.
3201  */
3202 class admin_setting_special_gradeexport extends admin_setting_configmulticheckbox {
3203     public function __construct() {
3204         parent::__construct('gradeexport', get_string('gradeexport', 'admin'),
3205                             get_string('configgradeexport', 'admin'), array(), NULL);
3206     }
3208     public function load_choices() {
3209         if (is_array($this->choices)) {
3210             return true;
3211         }
3212         $this->choices = array();
3214         if ($plugins = get_list_of_plugins('grade/export')) {
3215             foreach($plugins as $plugin) {
3216                 $this->choices[$plugin] = get_string('modulename', 'gradeexport_'.$plugin);
3217             }
3218         }
3219         return true;
3220     }
3223 /**
3224  * Grade category settings
3225  */
3226 class admin_setting_gradecat_combo extends admin_setting {
3228     public $choices;
3230     public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3231         $this->choices = $choices;
3232         parent::__construct($name, $visiblename, $description, $defaultsetting);
3233     }
3235     public function get_setting() {
3236         global $CFG;
3238         $value = $this->config_read($this->name);
3239         $flag  = $this->config_read($this->name.'_flag');
3241         if (is_null($value) or is_null($flag)) {
3242             return NULL;
3243         }
3245         $flag   = (int)$flag;
3246         $forced = (boolean)(1 & $flag); // first bit
3247         $adv    = (boolean)(2 & $flag); // second bit
3249         return array('value' => $value, 'forced' => $forced, 'adv' => $adv);
3250     }
3252     public function write_setting($data) {
3253         global $CFG;
3255         $value  = $data['value'];
3256         $forced = empty($data['forced']) ? 0 : 1;
3257         $adv    = empty($data['adv'])    ? 0 : 2;
3258         $flag   = ($forced | $adv); //bitwise or
3260         if (!in_array($value, array_keys($this->choices))) {
3261             return 'Error setting ';
3262         }
3264         $oldvalue  = $this->config_read($this->name);
3265         $oldflag   = (int)$this->config_read($this->name.'_flag');
3266         $oldforced = (1 & $oldflag); // first bit
3268         $result1 = $this->config_write($this->name, $value);
3269         $result2 = $this->config_write($this->name.'_flag', $flag);
3271         // force regrade if needed
3272         if ($oldforced != $forced or ($forced and $value != $oldvalue)) {
3273            require_once($CFG->libdir.'/gradelib.php');
3274            grade_category::updated_forced_settings();
3275         }
3277         if ($result1 and $result2) {
3278             return '';
3279         } else {
3280             return get_string('errorsetting', 'admin');
3281         }
3282     }
3284     public function output_html($data, $query='') {
3285         $value  = $data['value'];
3286         $forced = !empty($data['forced']);
3287         $adv    = !empty($data['adv']);
3289         $default = $this->get_defaultsetting();
3290         if (!is_null($default)) {
3291             $defaultinfo = array();
3292             if (isset($this->choices[$default['value']])) {
3293                 $defaultinfo[] = $this->choices[$default['value']];
3294             }
3295             if (!empty($default['forced'])) {
3296                 $defaultinfo[] = get_string('force');
3297             }
3298             if (!empty($default['adv'])) {
3299                 $defaultinfo[] = get_string('advanced');
3300             }
3301             $defaultinfo = implode(', ', $defaultinfo);
3303         } else {
3304             $defaultinfo = NULL;
3305         }
3308         $return = '<div class="form-group">';
3309         $return .= '<select class="form-select" id="'.$this->get_id().'" name="'.$this->get_full_name().'[value]">';
3310         foreach ($this->choices as $key => $val) {
3311             // the string cast is needed because key may be integer - 0 is equal to most strings!
3312             $return .= '<option value="'.$key.'"'.((string)$key==$value ? ' selected="selected"' : '').'>'.$val.'</option>';
3313         }
3314         $return .= '</select>';
3315         $return .= '<input type="checkbox" class="form-checkbox" id="'.$this->get_id().'force" name="'.$this->get_full_name().'[forced]" value="1" '.($forced ? 'checked="checked"' : '').' />'
3316                   .'<label for="'.$this->get_id().'force">'.get_string('force').'</label>';
3317         $return .= '<input type="checkbox" class="form-checkbox" id="'.$this->get_id().'adv" name="'.$this->get_full_name().'[adv]" value="1" '.($adv ? 'checked="checked"' : '').' />'
3318                   .'<label for="'.$this->get_id().'adv">'.get_string('advanced').'</label>';
3319         $return .= '</div>';
3321         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
3322     }
3326 /**
3327  * Selection of grade report in user profiles
3328  */
3329 class admin_setting_grade_profilereport extends admin_setting_configselect {
3330     public function __construct() {
3331         parent::__construct('grade_profilereport', get_string('profilereport', 'grades'), get_string('configprofilereport', 'grades'), 'user', null);
3332     }
3334     public function load_choices() {
3335         if (is_array($this->choices)) {
3336             return true;
3337         }
3338         $this->choices = array();
3340         global $CFG;
3341         require_once($CFG->libdir.'/gradelib.php');
3343         foreach (get_list_of_plugins('grade/report') as $plugin) {
3344             if (file_exists($CFG->dirroot.'/grade/report/'.$plugin.'/lib.php')) {
3345                 require_once($CFG->dirroot.'/grade/report/'.$plugin.'/lib.php');
3346                 $functionname = 'grade_report_'.$plugin.'_profilereport';
3347                 if (function_exists($functionname)) {
3348                     $this->choices[$plugin] = get_string('modulename', 'gradereport_'.$plugin);
3349                 }
3350             }
3351         }
3352         return true;
3353     }
3356 /**
3357  * Special class for register auth selection
3358  */
3359 class admin_setting_special_registerauth extends admin_setting_configselect {
3360     public function __construct() {
3361         parent::__construct('registerauth', get_string('selfregistration', 'auth'), get_string('selfregistration_help', 'auth'), '', null);
3362     }
3364     public function get_defaultsetting() {
3365         $this->load_choices();
3366         $defaultsetting = parent::get_defaultsetting();
3367         if (array_key_exists($defaultsetting, $this->choices)) {
3368             return $defaultsetting;
3369         } else {
3370             return '';
3371         }
3372     }
3374     public function load_choices() {
3375         global $CFG;
3377         if (is_array($this->choices)) {
3378             return true;
3379         }
3380         $this->choices = array();
3381         $this->choices[''] = get_string('disable');
3383         $authsenabled = get_enabled_auth_plugins(true);
3385         foreach ($authsenabled as $auth) {
3386             $authplugin = get_auth_plugin($auth);
3387             if (!$authplugin->can_signup()) {
3388                 continue;
3389             }
3390             // Get the auth title (from core or own auth lang files)
3391             $authtitle = $authplugin->get_title();
3392             $this->choices[$auth] = $authtitle;
3393         }
3394         return true;
3395     }
3398 /**
3399  * Module manage page
3400  */
3401 class admin_page_managemods extends admin_externalpage {
3402     public function __construct() {
3403         global $CFG;
3404         parent::__construct('managemodules', get_string('modsettings', 'admin'), "$CFG->wwwroot/$CFG->admin/modules.php");
3405     }
3407     public function search($query) {
3408         global $DB;
3409         if ($result = parent::search($query)) {
3410             return $result;
3411         }
3413         $found = false;
3414         if ($modules = $DB->get_records('modules')) {
3415             $textlib = textlib_get_instance();
3416             foreach ($modules as $module) {
3417                 if (strpos($module->name, $query) !== false) {
3418                     $found = true;
3419                     break;
3420                 }
3421                 $strmodulename = get_string('modulename', $module->name);
3422                 if (strpos($textlib->strtolower($strmodulename), $query) !== false) {
3423                     $found = true;
3424                     break;
3425                 }
3426             }
3427         }
3428         if ($found) {
3429             $result = new object();
3430             $result->page     = $this;
3431             $result->settings = array();
3432             return array($this->name => $result);
3433         } else {
3434             return array();
3435         }
3436     }
3439 /**
3440  * Enrolment manage page
3441  */
3442 class admin_enrolment_page extends admin_externalpage {
3443     public function __construct() {
3444         global $CFG;
3445         parent::__construct('enrolment', get_string('enrolments'), $CFG->wwwroot . '/'.$CFG->admin.'/enrol.php');
3446     }
3448     public function search($query) {
3449         if ($result = parent::search($query)) {
3450             return $result;
3451         }
3453         $found = false;
3455         if ($modules = get_list_of_plugins('enrol')) {
3456             $textlib = textlib_get_instance();
3457             foreach ($modules as $plugin) {
3458                 if (strpos($plugin, $query) !== false) {
3459                     $found = true;
3460                     break;
3461                 }
3462                 $strmodulename = get_string('enrolname', "enrol_$plugin");
3463                 if (strpos($textlib->strtolower($strmodulename), $query) !== false) {
3464                     $found = true;
3465                     break;
3466                 }
3467             }
3468         }
3469         //ugly harcoded hacks
3470         if (strpos('sendcoursewelcomemessage', $query) !== false) {
3471              $found = true;
3472         } else if (strpos($textlib->strtolower(get_string('sendcoursewelcomemessage', 'admin')), $query) !== false) {
3473              $found = true;
3474         } else if (strpos($textlib->strtolower(get_string('configsendcoursewelcomemessage', 'admin')), $query) !== false) {
3475              $found = true;
3476         } else if (strpos($textlib->strtolower(get_string('configenrolmentplugins', 'admin')), $query) !== false) {
3477              $found = true;
3478         }
3479         if ($found) {
3480             $result = new object();
3481             $result->page     = $this;
3482             $result->settings = array();
3483             return array($this->name => $result);
3484         } else {
3485             return array();
3486         }
3487     }
3490 /**
3491  * Blocks manage page
3492  */
3493 class admin_page_manageblocks extends admin_externalpage {
3494     public function __construct() {
3495         global $CFG;
3496         parent::__construct('manageblocks', get_string('blocksettings', 'admin'), "$CFG->wwwroot/$CFG->admin/blocks.php");
3497     }
3499     public function search($query) {
3500         global $CFG, $DB;
3501         if ($result = parent::search($query)) {
3502             return $result;
3503         }
3505         $found = false;
3506         if ($blocks = $DB->get_records('block')) {
3507             $textlib = textlib_get_instance();
3508             foreach ($blocks as $block) {
3509                 if (strpos($block->name, $query) !== false) {
3510                     $found = true;
3511                     break;
3512                 }
3513                 $strblockname = get_string('blockname', 'block_'.$block->name);
3514                 if (strpos($textlib->strtolower($strblockname), $query) !== false) {
3515                     $found = true;
3516                     break;
3517                 }
3518             }
3519         }
3520         if ($found) {
3521             $result = new object();
3522             $result->page     = $this;
3523             $result->settings = array();
3524             return array($this->name => $result);
3525         } else {
3526             return array();
3527         }
3528     }
3531 /**
3532  * Question type manage page
3533  */
3534 class admin_page_manageqtypes extends admin_externalpage {
3535     public function __construct() {
3536         global $CFG;
3537         parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'), "$CFG->wwwroot/$CFG->admin/qtypes.php");
3538     }
3540     public function search($query) {
3541         global $CFG;
3542         if ($result = parent::search($query)) {
3543             return $result;
3544         }
3546         $found = false;
3547         $textlib = textlib_get_instance();
3548         require_once($CFG->libdir . '/questionlib.php');
3549         global $QTYPES;
3550         foreach ($QTYPES as $qtype) {
3551             if (strpos($textlib->strtolower($qtype->local_name()), $query) !== false) {
3552                 $found = true;
3553                 break;
3554             }
3555         }
3556         if ($found) {
3557             $result = new object();
3558             $result->page     = $this;
3559             $result->settings = array();
3560             return array($this->name => $result);
3561         } else {
3562             return array();
3563         }
3564     }
3567 /**
3568  * Special class for authentication administration.
3569  */
3570 class admin_setting_manageauths extends admin_setting {
3571     public function __construct() {
3572         parent::__construct('authsui', get_string('authsettings', 'admin'), '', '');
3573     }
3575     public function get_setting() {
3576         return true;
3577     }
3579     public function get_defaultsetting() {
3580         return true;
3581     }
3583     public function write_setting($data) {
3584         // do not write any setting
3585         return '';
3586     }
3588     public function is_related($query) {
3589         if (parent::is_related($query)) {
3590             return true;
3591         }
3593         $textlib = textlib_get_instance();
3594         $authsavailable = get_list_of_plugins('auth');
3595         foreach ($authsavailable as $auth) {
3596             if (strpos($auth, $query) !== false) {
3597                 return true;
3598             }
3599             $authplugin = get_auth_plugin($auth);
3600             $authtitle&nb