fe2a7cfcc362384c4bb8499e274931d91237d3ea
[moodle.git] / lib / adminlib.php
1 <?php
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 required XMLDB constants
13 require_once($CFG->libdir.'/xmldb/xmldb_constants.php');
15 /// Add required XMLDB DB classes
16 require_once($CFG->libdir.'/xmldb/xmldb_object.php');
17 require_once($CFG->libdir.'/xmldb/xmldb_file.php');
18 require_once($CFG->libdir.'/xmldb/xmldb_structure.php');
19 require_once($CFG->libdir.'/xmldb/xmldb_table.php');
20 require_once($CFG->libdir.'/xmldb/xmldb_field.php');
21 require_once($CFG->libdir.'/xmldb/xmldb_key.php');
22 require_once($CFG->libdir.'/xmldb/xmldb_index.php');
23 require_once($CFG->libdir.'/xmldb/xmldb_statement.php');
25 /// Add other libraries
26 require_once($CFG->libdir.'/xmlize.php');
27 require_once($CFG->libdir .'/messagelib.php');      // Messagelib functions
29 global $upgradeloghandle, $upgradelogbuffer;
31 $upgradeloghandle = false;
32 $upgradelogbuffer = '';
34 define('INSECURE_DATAROOT_WARNING', 1);
35 define('INSECURE_DATAROOT_ERROR', 2);
37 /**
38  * Upgrade savepoint, marks end of each upgrade block.
39  * It stores new main version, resets upgrade timeout
40  * and abort upgrade if user cancels page loading.
41  *
42  * Please do not make large upgrade blocks with lots of operations,
43  * for example when adding tables keep only one table operation per block.
44  *
45  * @param bool $result false if upgrade step failed, true if completed
46  * @param string or float $version main version
47  * @return void
48  */
49 function upgrade_main_savepoint($result, $version) {
50     global $CFG;
52     if ($result) {
53         if ($CFG->version >= $version) {
54             // something really wrong is going on in main upgrade script
55             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$CFG->version, 'newversion'=>$version));
56         }
57         set_config('version', $version);
58     } else {
59         notify ("Upgrade savepoint: Error during main upgrade to version $version");
60     }
62     // reset upgrade timeout to default
63     upgrade_set_timeout();
65     // this is a safe place to stop upgrades if user aborts page loading
66     if (connection_aborted()) {
67         die;
68     }
69 }
71 /**
72  * Module upgrade savepoint, marks end of module upgrade blocks
73  * It stores module version, resets upgrade timeout
74  * and abort upgrade if usercancels page loading.
75  *
76  * @param bool $result false if upgrade step failed, true if completed
77  * @param string or float $version main version
78  * @return void
79  */
80 function upgrade_mod_savepoint($result, $version, $modname) {
81     global $DB;
83     if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
84         print_error('modulenotexist', 'debug', '', $modname);
85     }
87     if ($result) {
88         if ($module->version >= $version) {
89             // something really wrong is going on in upgrade script
90             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$module->version, 'newversion'=>$version));
91         }
92         $module->verions = $version;
93         $DB->update_record('modules', $module);
94     } else {
95         notify ("Upgrade savepoint: Error during mod upgrade to version $version");
96     }
98     // reset upgrade timeout to default
99     upgrade_set_timeout();
101     // this is a safe place to stop upgrades if user aborts page loading
102     if (connection_aborted()) {
103         die;
104     }
107 function upgrade_blocks_savepoint($result, $version, $blockname) {
108     global $DB;
110     if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
111         print_error('blocknotexist', 'debug', '', $blockname);
112     }
114     if ($result) {
115         if ($block->version >= $version) {
116             // something really wrong is going on in upgrade script
117             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$block->version, 'newversion'=>$version));
118         }
119         $block->verions = $version;
120         $DB->update_record('block', $block);
121     } else {
122         notify ("Upgrade savepoint: Error during mod upgrade to version $version");
123     }
125     // reset upgrade timeout to default
126     upgrade_set_timeout();
128     // this is a safe place to stop upgrades if user aborts page loading
129     if (connection_aborted()) {
130         die;
131     }
134 function upgrade_plugin_savepoint($result, $version, $type, $dir) {
135     //TODO
138 function upgrade_backup_savepoint($result, $version) {
139     //TODO
142 /**
143  * Delete all plugin tables
144  * @name string name of plugin, used as table prefix
145  * @file string path to install.xml file
146  * @feedback boolean
147  */
148 function drop_plugin_tables($name, $file, $feedback=true) {
149     global $CFG, $DB;
151     // first try normal delete
152     if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
153         return true;
154     }
156     // then try to find all tables that start with name and are not in any xml file
157     $used_tables = get_used_table_names();
159     $tables = $DB->get_tables();
161     /// Iterate over, fixing id fields as necessary
162     foreach ($tables as $table) {
163         if (in_array($table, $used_tables)) {
164             continue;
165         }
167         if (strpos($table, $name) !== 0) {
168             continue;
169         }
171         // found orphan table --> delete it
172         if ($DB->get_manager()->table_exists($table)) {
173             $xmldb_table = new xmldb_table($table);
174             $DB->get_manager()->drop_table($xmldb_table);
175         }
176     }
178     return true;
181 /**
182  * Returns names of all known tables == tables that moodle knowns about.
183  * @return array of lowercase table names
184  */
185 function get_used_table_names() {
186     $table_names = array();
187     $dbdirs = get_db_directories();
189     foreach ($dbdirs as $dbdir) {
190         $file = $dbdir.'/install.xml';
192         $xmldb_file = new xmldb_file($file);
194         if (!$xmldb_file->fileExists()) {
195             continue;
196         }
198         $loaded    = $xmldb_file->loadXMLStructure();
199         $structure =& $xmldb_file->getStructure();
201         if ($loaded and $tables = $structure->getTables()) {
202             foreach($tables as $table) {
203                 $table_names[] = strtolower($table->name);
204             }
205         }
206     }
208     return $table_names;
211 /**
212  * Returns list of all directories where we expect install.xml files
213  * @return array of paths
214  */
215 function get_db_directories() {
216     global $CFG;
218     $dbdirs = array();
220 /// First, the main one (lib/db)
221     $dbdirs[] = $CFG->libdir.'/db';
223 /// Now, activity modules (mod/xxx/db)
224     if ($plugins = get_list_of_plugins('mod')) {
225         foreach ($plugins as $plugin) {
226             $dbdirs[] = $CFG->dirroot.'/mod/'.$plugin.'/db';
227         }
228     }
230 /// Now, assignment submodules (mod/assignment/type/xxx/db)
231     if ($plugins = get_list_of_plugins('mod/assignment/type')) {
232         foreach ($plugins as $plugin) {
233             $dbdirs[] = $CFG->dirroot.'/mod/assignment/type/'.$plugin.'/db';
234         }
235     }
237 /// Now, question types (question/type/xxx/db)
238     if ($plugins = get_list_of_plugins('question/type')) {
239         foreach ($plugins as $plugin) {
240             $dbdirs[] = $CFG->dirroot.'/question/type/'.$plugin.'/db';
241         }
242     }
244 /// Now, backup/restore stuff (backup/db)
245     $dbdirs[] = $CFG->dirroot.'/backup/db';
247 /// Now, block system stuff (blocks/db)
248     $dbdirs[] = $CFG->dirroot.'/blocks/db';
250 /// Now, blocks (blocks/xxx/db)
251     if ($plugins = get_list_of_plugins('blocks', 'db')) {
252         foreach ($plugins as $plugin) {
253             $dbdirs[] = $CFG->dirroot.'/blocks/'.$plugin.'/db';
254         }
255     }
257 /// Now, course formats (course/format/xxx/db)
258     if ($plugins = get_list_of_plugins('course/format', 'db')) {
259         foreach ($plugins as $plugin) {
260             $dbdirs[] = $CFG->dirroot.'/course/format/'.$plugin.'/db';
261         }
262     }
264 /// Now, enrolment plugins (enrol/xxx/db)
265     if ($plugins = get_list_of_plugins('enrol', 'db')) {
266         foreach ($plugins as $plugin) {
267             $dbdirs[] = $CFG->dirroot.'/enrol/'.$plugin.'/db';
268         }
269     }
271 /// Now admin report plugins (admin/report/xxx/db)
272     if ($plugins = get_list_of_plugins($CFG->admin.'/report', 'db')) {
273         foreach ($plugins as $plugin) {
274             $dbdirs[] = $CFG->dirroot.'/'.$CFG->admin.'/report/'.$plugin.'/db';
275         }
276     }
278 /// Now quiz report plugins (mod/quiz/report/xxx/db)
279     if ($plugins = get_list_of_plugins('mod/quiz/report', 'db')) {
280         foreach ($plugins as $plugin) {
281             $dbdirs[] = $CFG->dirroot.'/mod/quiz/report/'.$plugin.'/db';
282         }
283     }
285 /// Local database changes, if the local folder exists.
286     if (file_exists($CFG->dirroot . '/local')) {
287         $dbdirs[] = $CFG->dirroot.'/local/db';
288     }
290     return $dbdirs;
293 /**
294  * Upgrade plugins
295  *
296  * @uses $CFG
297  * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
298  * @param string $dir  The directory where the plugins are located (e.g. 'question/questiontypes')
299  * @param string $return The url to prompt the user to continue to
300  */
301 function upgrade_plugins($type, $dir, $return) {
302     global $CFG, $interactive, $DB;
304 /// Let's know if the header has been printed, so the funcion is being called embedded in an outer page
305     $embedded = defined('HEADER_PRINTED');
307     $plugs = get_list_of_plugins($dir);
308     $updated_plugins = false;
309     $strpluginsetup  = get_string('pluginsetup');
311     foreach ($plugs as $plug) {
313         $fullplug = $CFG->dirroot .'/'.$dir.'/'. $plug;
315         unset($plugin);
317         if (is_readable($fullplug .'/version.php')) {
318             include_once($fullplug .'/version.php');  // defines $plugin with version etc
319         } else {
320             continue;                              // Nothing to do.
321         }
323         $newupgrade = false;
324         if (is_readable($fullplug . '/db/upgrade.php')) {
325             include_once($fullplug . '/db/upgrade.php');  // defines new upgrading function
326             $newupgrade = true;
327         }
329         if (!isset($plugin)) {
330             continue;
331         }
333         if (!empty($plugin->requires)) {
334             if ($plugin->requires > $CFG->version) {
335                 $info = new object();
336                 $info->pluginname = $plug;
337                 $info->pluginversion  = $plugin->version;
338                 $info->currentmoodle = $CFG->version;
339                 $info->requiremoodle = $plugin->requires;
340                 if (!$updated_plugins && !$embedded) {
341                     print_header($strpluginsetup, $strpluginsetup,
342                         build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
343                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
344                 }
345                 upgrade_log_start();
346                 notify(get_string('pluginrequirementsnotmet', 'error', $info));
347                 $updated_plugins = true;
348                 continue;
349             }
350         }
352         $plugin->name = $plug;   // The name MUST match the directory
354         $pluginversion = $type.'_'.$plug.'_version';
356         if (!isset($CFG->$pluginversion)) {
357             set_config($pluginversion, 0);
358         }
360         if ($CFG->$pluginversion == $plugin->version) {
361             // do nothing
362         } else if ($CFG->$pluginversion < $plugin->version) {
363             if (!$updated_plugins && !$embedded) {
364                 print_header($strpluginsetup, $strpluginsetup,
365                         build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
366                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
367             }
368             $updated_plugins = true;
369             upgrade_log_start();
370             print_heading($dir.'/'. $plugin->name .' plugin needs upgrading');
371             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
372                 $DB->set_debug(true);
373             }
374             @set_time_limit(0);  // To allow slow databases to complete the long SQL
376             if ($CFG->$pluginversion == 0) {    // It's a new install of this plugin
377             /// Both old .sql files and new install.xml are supported
378             /// but we priorize install.xml (XMLDB) if present
379                 if (file_exists($fullplug . '/db/install.xml')) {
380                     $DB->get_manager()->install_from_xmldb_file($fullplug . '/db/install.xml'); //New method
381                 }
382                 $status = true;
383                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
384                     $DB->set_debug(false);
385                 }
386             /// Continue with the instalation, roles and other stuff
387                 if ($status) {
388                 /// OK so far, now update the plugins record
389                     set_config($pluginversion, $plugin->version);
391                 /// Install capabilities
392                     if (!update_capabilities($type.'/'.$plug)) {
393                         print_error('cannotsetupcapforplugin', '', '', $plugin->name);
394                     }
395                 /// Install events
396                     events_update_definition($type.'/'.$plug);
398                 /// Install message providers
399                     message_update_providers($type.'/'.$plug);
401                 /// Run local install function if there is one
402                     if (is_readable($fullplug .'/lib.php')) {
403                         include_once($fullplug .'/lib.php');
404                         $installfunction = $plugin->name.'_install';
405                         if (function_exists($installfunction)) {
406                             if (! $installfunction() ) {
407                                 notify('Encountered a problem running install function for '.$module->name.'!');
408                             }
409                         }
410                     }
412                     notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
413                 } else {
414                     notify('Installing '. $plugin->name .' FAILED!');
415                 }
416             } else {                            // Upgrade existing install
417             /// Run de old and new upgrade functions for the module
418                 $newupgrade_function = 'xmldb_' . $type.'_'.$plugin->name .'_upgrade';
420             /// Then, the new function if exists and the old one was ok
421                 $newupgrade_status = true;
422                 if ($newupgrade && function_exists($newupgrade_function)) {
423                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
424                         $DB->set_debug(true);
425                     }
426                     $newupgrade_status = $newupgrade_function($CFG->$pluginversion);
427                 } else if ($newupgrade) {
428                     notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
429                              $fullplug . '/db/upgrade.php');
430                 }
431                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
432                     $DB->set_debug(false);
433                 }
434             /// Now analyze upgrade results
435                 if ($newupgrade_status) {    // No upgrading failed
436                 /// OK so far, now update the plugins record
437                     set_config($pluginversion, $plugin->version);
438                     if (!update_capabilities($type.'/'.$plug)) {
439                         print_error('cannotupdateplugincap', '', '', $plugin->name);
440                     }
441                 /// Update events
442                     events_update_definition($type.'/'.$plug);
444                 /// Update message providers
445                     message_update_providers($type.'/'.$plug);
447                     notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
448                 } else {
449                     notify('Upgrading '. $plugin->name .' from '. $CFG->$pluginversion .' to '. $plugin->version .' FAILED!');
450                 }
451             }
452             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
453                 echo '<hr />';
454             }
455         } else {
456             upgrade_log_start();
457             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$CFG->pluginversion, 'newversion'=>$plugin->version));
458         }
459     }
461     upgrade_log_finish();
463     if ($updated_plugins && !$embedded) {
464         if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
465             print_continue($return);
466             print_footer('none');
467             die;
468         } else if (CLI_UPGRADE && ($interactive > CLI_SEMI )) {
469             console_write(STDOUT,'askcontinue');
470             if (read_boolean()){
471                 return ;
472             } else {
473                 console_write(STDERR,'','',false);
474             }
475         }
476     }
479 /**
480  * Find and check all modules and load them up or upgrade them if necessary
481  *
482  * @uses $CFG
483  * @param string $return The url to prompt the user to continue to
484  * @todo Finish documenting this function
485  */
486 function upgrade_activity_modules($return) {
488     global $CFG, $interactive, $DB;
490     if (!$mods = get_list_of_plugins('mod') ) {
491         print_error('nomodules', 'debug');
492     }
494     $updated_modules = false;
495     $strmodulesetup  = get_string('modulesetup');
497     foreach ($mods as $mod) {
499         if ($mod == 'NEWMODULE') {   // Someone has unzipped the template, ignore it
500             continue;
501         }
503         $fullmod = $CFG->dirroot .'/mod/'. $mod;
505         unset($module);
507         if ( is_readable($fullmod .'/version.php')) {
508             include_once($fullmod .'/version.php');  // defines $module with version etc
509         } else {
510             notify('Module '. $mod .': '. $fullmod .'/version.php was not readable');
511             continue;
512         }
514         $newupgrade = false;
515         if ( is_readable($fullmod . '/db/upgrade.php')) {
516             include_once($fullmod . '/db/upgrade.php');  // defines new upgrading function
517             $newupgrade = true;
518         }
520         if (!isset($module)) {
521             continue;
522         }
524         if (!empty($module->requires)) {
525             if ($module->requires > $CFG->version) {
526                 $info = new object();
527                 $info->modulename = $mod;
528                 $info->moduleversion  = $module->version;
529                 $info->currentmoodle = $CFG->version;
530                 $info->requiremoodle = $module->requires;
531                 if (!$updated_modules) {
532                     print_header($strmodulesetup, $strmodulesetup,
533                             build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
534                             upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
535                 }
536                 upgrade_log_start();
537                 notify(get_string('modulerequirementsnotmet', 'error', $info));
538                 $updated_modules = true;
539                 continue;
540             }
541         }
543         $module->name = $mod;   // The name MUST match the directory
545         include_once($fullmod.'/lib.php');  // defines upgrading and/or installing functions
547         if ($currmodule = $DB->get_record('modules', array('name'=>$module->name))) {
548             if ($currmodule->version == $module->version) {
549                 // do nothing
550             } else if ($currmodule->version < $module->version) {
551             /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
552                 if (!$newupgrade) {
553                     notify('Upgrade file ' . $mod . ': ' . $fullmod . '/db/upgrade.php is not readable');
554                     continue;
555                 }
556                 if (!$updated_modules) {
557                     print_header($strmodulesetup, $strmodulesetup,
558                             build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
559                             upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
560                 }
561                 upgrade_log_start();
563                 print_heading($module->name .' module needs upgrading');
565             /// Run de old and new upgrade functions for the module
566                 $newupgrade_function = 'xmldb_' . $module->name . '_upgrade';
568             /// Then, the new function if exists and the old one was ok
569                 $newupgrade_status = true;
570                 if ($newupgrade && function_exists($newupgrade_function)) {
571                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
572                         $DB->set_debug(true);
573                     }
574                     $newupgrade_status = $newupgrade_function($currmodule->version, $module);
575                 } else if ($newupgrade) {
576                     notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
577                              $mod . ': ' . $fullmod . '/db/upgrade.php');
578                 }
579                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
580                     $DB->set_debug(false);
581                 }
582             /// Now analyze upgrade results
583                 if ($newupgrade_status) {    // No upgrading failed
584                     // OK so far, now update the modules record
585                     $module->id = $currmodule->id;
586                     if (!$DB->update_record('modules', $module)) {
587                         print_error('cannotupdatemod', '', '', $module->name);
588                     }
589                     remove_dir($CFG->dataroot . '/cache', true); // flush cache
590                     notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
591                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE) {
592                        echo '<hr />';
593                     }
594                 } else {
595                     notify('Upgrading '. $module->name .' from '. $currmodule->version .' to '. $module->version .' FAILED!');
596                 }
598             /// Update the capabilities table?
599                 if (!update_capabilities('mod/'.$module->name)) {
600                     print_error('cannotupdatemodcap', '', '', $module->name);
601                 }
603             /// Update events
604                 events_update_definition('mod/'.$module->name);
606             /// Update message providers
607                 message_update_providers('mod/'.$module->name);
609                 $updated_modules = true;
611             } else {
612                 upgrade_log_start();
613                 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$currmodule->version, 'newversion'=>$module->version));
614             }
616         } else {    // module not installed yet, so install it
617             if (!$updated_modules) {
618                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
619                     print_header($strmodulesetup, $strmodulesetup,
620                         build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
621                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
622                 }
623             }
624             upgrade_log_start();
625             print_heading($module->name);
626             $updated_modules = true;
627             // To avoid unnecessary output from the SQL queries in the CLI version
628             if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
629                 $DB->set_debug(true);
630             }
631             @set_time_limit(0);  // To allow slow databases to complete the long SQL
633         /// Both old .sql files and new install.xml are supported
634         /// but we priorize install.xml (XMLDB) if present
635             if (file_exists($fullmod . '/db/install.xml')) {
636                 $DB->get_manager()->install_from_xmldb_file($fullmod . '/db/install.xml'); //New method
637                 $status = true;
638             }
639             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
640                 $DB->set_debug(false);
641             }
643         /// Continue with the installation, roles and other stuff
644             if ($status) {
645                 if ($module->id = $DB->insert_record('modules', $module)) {
647                 /// Capabilities
648                     if (!update_capabilities('mod/'.$module->name)) {
649                         print_error('cannotsetupcapformod', '', '', $module->name);
650                     }
652                 /// Events
653                     events_update_definition('mod/'.$module->name);
655                 /// Message providers
656                     message_update_providers('mod/'.$module->name);
658                 /// Run local install function if there is one
659                     $installfunction = $module->name.'_install';
660                     if (function_exists($installfunction)) {
661                         if (! $installfunction() ) {
662                             notify('Encountered a problem running install function for '.$module->name.'!');
663                         }
664                     }
666                     notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
667                     if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
668                        echo '<hr />';
669                     }
670                 } else {
671                     print_error('cannotaddmodule', '', '', $module->name);
672                 }
673             } else {
674                 print_error('cannotsetuptable', 'debug', '', $module->name);
675             }
676         }
678     /// Check submodules of this module if necessary
680         $submoduleupgrade = $module->name.'_upgrade_submodules';
681         if (function_exists($submoduleupgrade)) {
682             $submoduleupgrade();
683         }
685     /// Run any defaults or final code that is necessary for this module
687         if ( is_readable($fullmod .'/defaults.php')) {
688             // Insert default values for any important configuration variables
689             unset($defaults);
690             include($fullmod .'/defaults.php'); // include here means execute, not library include
691             if (!empty($defaults)) {
692                 if (!empty($defaults['_use_config_plugins'])) {
693                     unset($defaults['_use_config_plugins']);
694                     $localcfg = get_config($module->name);
695                     foreach ($defaults as $name => $value) {
696                         if (!isset($localcfg->$name)) {
697                             set_config($name, $value, $module->name);
698                         }
699                     }
700                 } else {
701                     foreach ($defaults as $name => $value) {
702                         if (!isset($CFG->$name)) {
703                             set_config($name, $value);
704                         }
705                     }
706                 }
707             }
708         }
709     }
711     upgrade_log_finish(); // finish logging if started
713     if ($updated_modules) {
714         if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
715             print_continue($return);
716             print_footer('none');
717             die;
718         } else if ( CLI_UPGRADE && ($interactive > CLI_SEMI) ) {
719             console_write(STDOUT,'askcontinue');
720             if (read_boolean()){
721                 return ;
722             }else {
723                 console_write(STDERR,'','',false);
724             }
725         }
726     }
729 /**
730  * Try to obtain or release the cron lock.
731  *
732  * @param string  $name  name of lock
733  * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionaly
734  * @param bool $ignorecurrent ignore current lock state, usually entend previous lock
735  * @return bool true if lock obtained
736  */
737 function set_cron_lock($name, $until, $ignorecurrent=false) {
738     global $DB;
739     if (empty($name)) {
740         debugging("Tried to get a cron lock for a null fieldname");
741         return false;
742     }
744     // remove lock by force == remove from config table
745     if (is_null($until)) {
746         set_config($name, null);
747         return true;
748     }
750     if (!$ignorecurrent) {
751         // read value from db - other processes might have changed it
752         $value = $DB->get_field('config', 'value', array('name'=>$name));
754         if ($value and $value > time()) {
755             //lock active
756             return false;
757         }
758     }
760     set_config($name, $until);
761     return true;
764 function print_progress($done, $total, $updatetime=5, $sleeptime=1, $donetext='') {
765     static $thisbarid;
766     static $starttime;
767     static $lasttime;
769     if ($total < 2) {   // No need to show anything
770         return;
771     }
773     // Are we done?
774     if ($done >= $total) {
775         $done = $total;
776         if (!empty($thisbarid)) {
777             $donetext .= ' ('.$done.'/'.$total.') '.get_string('success');
778             print_progress_redraw($thisbarid, $done, $total, 500, $donetext);
779             $thisbarid = $starttime = $lasttime = NULL;
780         }
781         return;
782     }
784     if (empty($starttime)) {
785         $starttime = $lasttime = time();
786         $lasttime = $starttime - $updatetime;
787         $thisbarid = uniqid();
788         echo '<table width="500" cellpadding="0" cellspacing="0" align="center"><tr><td width="500">';
789         echo '<div id="bar'.$thisbarid.'" style="border-style:solid;border-width:1px;width:500px;height:50px;">';
790         echo '<div id="slider'.$thisbarid.'" style="border-style:solid;border-width:1px;height:48px;width:10px;background-color:green;"></div>';
791         echo '</div>';
792         echo '<div id="text'.$thisbarid.'" align="center" style="width:500px;"></div>';
793         echo '</td></tr></table>';
794         echo '</div>';
795     }
797     $now = time();
799     if ($done && (($now - $lasttime) >= $updatetime)) {
800         $elapsedtime = $now - $starttime;
801         $projectedtime = (int)(((float)$total / (float)$done) * $elapsedtime) - $elapsedtime;
802         $percentage = round((float)$done / (float)$total, 2);
803         $width = (int)(500 * $percentage);
805         if ($projectedtime > 10) {
806             $projectedtext = '  Ending: '.format_time($projectedtime);
807         } else {
808             $projectedtext = '';
809         }
811         $donetext .= ' ('.$done.'/'.$total.') '.$projectedtext;
812         print_progress_redraw($thisbarid, $done, $total, $width, $donetext);
814         $lasttime = $now;
815     }
818 // Don't call this function directly, it's called from print_progress.
819 function print_progress_redraw($thisbarid, $done, $total, $width, $donetext='') {
820     if (empty($thisbarid)) {
821         return;
822     }
823     echo '<script>';
824     echo 'document.getElementById("text'.$thisbarid.'").innerHTML = "'.addslashes_js($donetext).'";'."\n";
825     echo 'document.getElementById("slider'.$thisbarid.'").style.width = \''.$width.'px\';'."\n";
826     echo '</script>';
829 function upgrade_get_javascript() {
830     global $CFG, $SESSION;
832     if (!empty($SESSION->installautopilot)) {
833         $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = true;</script>'."\n";
834     } else {
835         $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = false;</script>'."\n";
836     }
837     $linktoscrolltoerrors .= '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>';
839     return $linktoscrolltoerrors;
842 function create_admin_user($user_input=NULL) {
843     global $CFG, $USER, $DB;
845     if (empty($CFG->rolesactive)) {   // No admin user yet.
847         $user = new object();
848         $user->auth         = 'manual';
849         $user->firstname    = get_string('admin');
850         $user->lastname     = get_string('user');
851         $user->username     = 'admin';
852         $user->password     = hash_internal_user_password('admin');
853         $user->email        = 'root@localhost';
854         $user->confirmed    = 1;
855         $user->mnethostid   = $CFG->mnet_localhost_id;
856         $user->lang         = $CFG->lang;
857         $user->maildisplay  = 1;
858         $user->timemodified = time();
860         if ($user_input) {
861             $user = $user_input;
862         }
863         if (!$user->id = $DB->insert_record('user', $user)) {
864             print_error('cannotcreateadminuser', 'debug');
865         }
867         if (!$user = $DB->get_record('user', array('id'=>$user->id))) {   // Double check.
868             print_error('invaliduserid');
869         }
871         // Assign the default admin roles to the new user.
872         if (!$adminroles = get_roles_with_capability('moodle/legacy:admin', CAP_ALLOW)) {
873             print_error('noadminrole', 'message');
874         }
875         $sitecontext = get_context_instance(CONTEXT_SYSTEM);
876         foreach ($adminroles as $adminrole) {
877             role_assign($adminrole->id, $user->id, 0, $sitecontext->id);
878         }
880         //set default message preferences
881         if (!message_set_default_message_preferences( $user )){
882             print_error('cannotsavemessageprefs', 'message');
883         }
885         set_config('rolesactive', 1);
887         // Log the user in.
888         $USER = get_complete_user_data('username', 'admin');
889         $USER->newadminuser = 1;
890         load_all_capabilities();
892         if (!defined('CLI_UPGRADE')||!CLI_UPGRADE) {
893           redirect("$CFG->wwwroot/user/editadvanced.php?id=$user->id");  // Edit thyself
894         }
895     } else {
896         print_error('cannotcreateadminuser', 'debug');
897     }
900 ////////////////////////////////////////////////
901 /// upgrade logging functions
902 ////////////////////////////////////////////////
904 /**
905  * Marks start of upgrade, blocks any other access to site.
906  * The upgrade is finished at the end of script or after timeout.
907  */
908 function start_upgrade() {
909     global $CFG;
911     static $started = false;
913     if ($started) {
914         upgrade_set_timeout(120);
916     } else {
917         ignore_user_abort(true);
918         register_shutdown_function('upgrade_finished_handler');
919         if ($CFG->version === '') {
920             // db not installed yet
921             $CFG->upgraderunning = time()+300;
922         } else {
923             set_config('upgraderunning', time()+300);
924         }
925         $started = true;
926     }
929 /**
930  * Internal function - executed at the very end of each upgrade.
931  */
932 function upgrade_finished_handler() {
933     upgrade_log_finish();
934     unset_config('upgraderunning');
935     ignore_user_abort(false);
938 /**
939  * Start logging of output into file (if not disabled) and
940  * prevent aborting and concurrent execution of upgrade script.
941  *
942  * Please note that you can not write into session variables after calling this function!
943  *
944  * This function may be called repeatedly.
945  */
946 function upgrade_log_start() {
947     global $upgradeloghandle;
949     start_upgrade(); // make sure the upgrade is started
951     if ($upgradeloghandle and ($upgradeloghandle !== 'error')) {
952         return;
953     }
955     make_upload_directory('upgradelogs');
956     ob_start('upgrade_log_callback', 2); // function for logging to disk; flush each line of text ASAP
959 /**
960  * Terminate logging of output, flush all data.
961  *
962  * Please make sure that each upgrade_log_start() is properly terminated by
963  * this function or print_error().
964  *
965  * This function may be called repeatedly.
966  */
967 function upgrade_log_finish() {
968     global $CFG, $upgradeloghandle, $upgradelogbuffer;
970     @ob_end_flush();
971     if ($upgradelogbuffer !== '') {
972         @fwrite($upgradeloghandle, $upgradelogbuffer);
973         $upgradelogbuffer = '';
974     }
975     if ($upgradeloghandle and ($upgradeloghandle !== 'error')) {
976         @fclose($upgradeloghandle);
977         $upgradeloghandle = false;
978     }
981 /**
982  * Callback function for logging into files. Not more than one file is created per minute,
983  * upgrade session (terminated by upgrade_log_finish()) is always stored in one file.
984  *
985  * This function must not output any characters or throw warnigns and errors!
986  */
987 function upgrade_log_callback($string) {
988     global $CFG, $upgradeloghandle, $upgradelogbuffer;
990     if (empty($CFG->disableupgradelogging) and ($string != '') and ($upgradeloghandle !== 'error')) {
991         if ($upgradeloghandle or ($upgradeloghandle = @fopen($CFG->dataroot.'/upgradelogs/upg_'.date('Ymd-Hi').'.html', 'a'))) {
992             $upgradelogbuffer .= $string;
993             if (strlen($upgradelogbuffer) > 2048) { // 2kB write buffer
994                 @fwrite($upgradeloghandle, $upgradelogbuffer);
995                 $upgradelogbuffer = '';
996             }
997         } else {
998             $upgradeloghandle = 'error';
999         }
1000     }
1001     return $string;
1004 /**
1005  * Test if and critical warnings are present
1006  * @return bool
1007  */
1008 function admin_critical_warnings_present() {
1009     global $SESSION;
1011     if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
1012         return 0;
1013     }
1015     if (!isset($SESSION->admin_critical_warning)) {
1016         $SESSION->admin_critical_warning = 0;
1017         if (ini_get_bool('register_globals')) {
1018             $SESSION->admin_critical_warning = 1;
1019         } else if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
1020             $SESSION->admin_critical_warning = 1;
1021         }
1022     }
1024     return $SESSION->admin_critical_warning;
1027 /**
1028  * Try to verify that dataroot is not accessible from web.
1029  * It is not 100% correct but might help to reduce number of vulnerable sites.
1030  *
1031  * Protection from httpd.conf and .htaccess is not detected properly.
1032  * @param bool $fetchtest try to test public access by fetching file
1033  * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING migth be problematic
1034  */
1035 function is_dataroot_insecure($fetchtest=false) {
1036     global $CFG;
1038     $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
1040     $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
1041     $rp = strrev(trim($rp, '/'));
1042     $rp = explode('/', $rp);
1043     foreach($rp as $r) {
1044         if (strpos($siteroot, '/'.$r.'/') === 0) {
1045             $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
1046         } else {
1047             break; // probably alias root
1048         }
1049     }
1051     $siteroot = strrev($siteroot);
1052     $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
1054     if (strpos($dataroot, $siteroot) !== 0) {
1055         return false;
1056     }
1058     if (!$fetchtest) {
1059         return INSECURE_DATAROOT_WARNING;
1060     }
1062     // now try all methods to fetch a test file using http protocol
1064     $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
1065     preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
1066     $httpdocroot = $matches[1];
1067     $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
1068     if (make_upload_directory('diag', false) === false) {
1069         return INSECURE_DATAROOT_WARNING;
1070     }
1071     $testfile = $CFG->dataroot.'/diag/public.txt';
1072     if (!file_exists($testfile)) {
1073         file_put_contents($testfile, 'test file, do not delete');
1074     }
1075     $teststr = trim(file_get_contents($testfile));
1076     if (empty($teststr)) {
1077         // hmm, strange
1078         return INSECURE_DATAROOT_WARNING;
1079     }
1081     $testurl = $datarooturl.'/diag/public.txt';
1083     if (extension_loaded('curl') and ($ch = @curl_init($testurl)) !== false) {
1084         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1085         curl_setopt($ch, CURLOPT_HEADER, false);
1086         $data = curl_exec($ch);
1087         if (!curl_errno($ch)) {
1088             $data = trim($data);
1089             if ($data === $teststr) {
1090                 curl_close($ch);
1091                 return INSECURE_DATAROOT_ERROR;
1092             }
1093         }
1094         curl_close($ch);
1095     }
1097     if ($data = @file_get_contents($testurl)) {
1098         $data = trim($data);
1099         if ($data === $teststr) {
1100             return INSECURE_DATAROOT_ERROR;
1101         }
1102     }
1104     preg_match('|https?://([^/]+)|i', $testurl, $matches);
1105     $sitename = $matches[1];
1106     $error = 0;
1107     if ($fp = @fsockopen($sitename, 80, $error)) {
1108         preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
1109         $localurl = $matches[1];
1110         $out = "GET $localurl HTTP/1.1\r\n";
1111         $out .= "Host: $sitename\r\n";
1112         $out .= "Connection: Close\r\n\r\n";
1113         fwrite($fp, $out);
1114         $data = '';
1115         $incoming = false;
1116         while (!feof($fp)) {
1117             if ($incoming) {
1118                 $data .= fgets($fp, 1024);
1119             } else if (@fgets($fp, 1024) === "\r\n") {
1120                 $incoming = true;
1121             }
1122         }
1123         fclose($fp);
1124         $data = trim($data);
1125         if ($data === $teststr) {
1126             return INSECURE_DATAROOT_ERROR;
1127         }
1128     }
1130     return INSECURE_DATAROOT_WARNING;
1133 /// =============================================================================================================
1134 /// administration tree classes and functions
1137 // n.b. documentation is still in progress for this code
1139 /// INTRODUCTION
1141 /// This file performs the following tasks:
1142 ///  -it defines the necessary objects and interfaces to build the Moodle
1143 ///   admin hierarchy
1144 ///  -it defines the admin_externalpage_setup(), admin_externalpage_print_header(),
1145 ///   and admin_externalpage_print_footer() functions used on admin pages
1147 /// ADMIN_SETTING OBJECTS
1149 /// Moodle settings are represented by objects that inherit from the admin_setting
1150 /// class. These objects encapsulate how to read a setting, how to write a new value
1151 /// to a setting, and how to appropriately display the HTML to modify the setting.
1153 /// ADMIN_SETTINGPAGE OBJECTS
1155 /// The admin_setting objects are then grouped into admin_settingpages. The latter
1156 /// appear in the Moodle admin tree block. All interaction with admin_settingpage
1157 /// objects is handled by the admin/settings.php file.
1159 /// ADMIN_EXTERNALPAGE OBJECTS
1161 /// There are some settings in Moodle that are too complex to (efficiently) handle
1162 /// with admin_settingpages. (Consider, for example, user management and displaying
1163 /// lists of users.) In this case, we use the admin_externalpage object. This object
1164 /// places a link to an external PHP file in the admin tree block.
1166 /// If you're using an admin_externalpage object for some settings, you can take
1167 /// advantage of the admin_externalpage_* functions. For example, suppose you wanted
1168 /// to add a foo.php file into admin. First off, you add the following line to
1169 /// admin/settings/first.php (at the end of the file) or to some other file in
1170 /// admin/settings:
1172 ///    $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
1173 ///        $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
1175 /// Next, in foo.php, your file structure would resemble the following:
1177 ///        require_once('.../config.php');
1178 ///        require_once($CFG->libdir.'/adminlib.php');
1179 ///        admin_externalpage_setup('foo');
1180 ///        // functionality like processing form submissions goes here
1181 ///        admin_externalpage_print_header();
1182 ///        // your HTML goes here
1183 ///        admin_externalpage_print_footer();
1185 /// The admin_externalpage_setup() function call ensures the user is logged in,
1186 /// and makes sure that they have the proper role permission to access the page.
1188 /// The admin_externalpage_print_header() function prints the header (it figures
1189 /// out what category and subcategories the page is classified under) and ensures
1190 /// that you're using the admin pagelib (which provides the admin tree block and
1191 /// the admin bookmarks block).
1193 /// The admin_externalpage_print_footer() function properly closes the tables
1194 /// opened up by the admin_externalpage_print_header() function and prints the
1195 /// standard Moodle footer.
1197 /// ADMIN_CATEGORY OBJECTS
1199 /// Above and beyond all this, we have admin_category objects. These objects
1200 /// appear as folders in the admin tree block. They contain admin_settingpage's,
1201 /// admin_externalpage's, and other admin_category's.
1203 /// OTHER NOTES
1205 /// admin_settingpage's, admin_externalpage's, and admin_category's all inherit
1206 /// from part_of_admin_tree (a pseudointerface). This interface insists that
1207 /// a class has a check_access method for access permissions, a locate method
1208 /// used to find a specific node in the admin tree and find parent path.
1210 /// admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
1211 /// interface ensures that the class implements a recursive add function which
1212 /// accepts a part_of_admin_tree object and searches for the proper place to
1213 /// put it. parentable_part_of_admin_tree implies part_of_admin_tree.
1215 /// Please note that the $this->name field of any part_of_admin_tree must be
1216 /// UNIQUE throughout the ENTIRE admin tree.
1218 /// The $this->name field of an admin_setting object (which is *not* part_of_
1219 /// admin_tree) must be unique on the respective admin_settingpage where it is
1220 /// used.
1223 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
1225 /**
1226  * Pseudointerface for anything appearing in the admin tree
1227  *
1228  * The pseudointerface that is implemented by anything that appears in the admin tree
1229  * block. It forces inheriting classes to define a method for checking user permissions
1230  * and methods for finding something in the admin tree.
1231  *
1232  * @author Vincenzo K. Marcovecchio
1233  * @package admin
1234  */
1235 class part_of_admin_tree {
1237     /**
1238      * Finds a named part_of_admin_tree.
1239      *
1240      * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
1241      * and not parentable_part_of_admin_tree, then this function should only check if
1242      * $this->name matches $name. If it does, it should return a reference to $this,
1243      * otherwise, it should return a reference to NULL.
1244      *
1245      * If a class inherits parentable_part_of_admin_tree, this method should be called
1246      * recursively on all child objects (assuming, of course, the parent object's name
1247      * doesn't match the search criterion).
1248      *
1249      * @param string $name The internal name of the part_of_admin_tree we're searching for.
1250      * @return mixed An object reference or a NULL reference.
1251      */
1252     function &locate($name) {
1253         trigger_error('Admin class does not implement method <strong>locate()</strong>', E_USER_WARNING);
1254         return;
1255     }
1257     /**
1258      * Removes named part_of_admin_tree.
1259      *
1260      * @param string $name The internal name of the part_of_admin_tree we want to remove.
1261      * @return bool success.
1262      */
1263     function prune($name) {
1264         trigger_error('Admin class does not implement method <strong>prune()</strong>', E_USER_WARNING);
1265         return;
1266     }
1268     /**
1269      * Search using query
1270      * @param strin query
1271      * @return mixed array-object structure of found settings and pages
1272      */
1273     function search($query) {
1274         trigger_error('Admin class does not implement method <strong>search()</strong>', E_USER_WARNING);
1275         return;
1276     }
1278     /**
1279      * Verifies current user's access to this part_of_admin_tree.
1280      *
1281      * Used to check if the current user has access to this part of the admin tree or
1282      * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
1283      * then this method is usually just a call to has_capability() in the site context.
1284      *
1285      * If a class inherits parentable_part_of_admin_tree, this method should return the
1286      * logical OR of the return of check_access() on all child objects.
1287      *
1288      * @return bool True if the user has access, false if she doesn't.
1289      */
1290     function check_access() {
1291         trigger_error('Admin class does not implement method <strong>check_access()</strong>', E_USER_WARNING);
1292         return;
1293     }
1295     /**
1296      * Mostly usefull for removing of some parts of the tree in admin tree block.
1297      *
1298      * @return True is hidden from normal list view
1299      */
1300     function is_hidden() {
1301         trigger_error('Admin class does not implement method <strong>is_hidden()</strong>', E_USER_WARNING);
1302         return;
1303     }
1306 /**
1307  * Pseudointerface implemented by any part_of_admin_tree that has children.
1308  *
1309  * The pseudointerface implemented by any part_of_admin_tree that can be a parent
1310  * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
1311  * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
1312  * include an add method for adding other part_of_admin_tree objects as children.
1313  *
1314  * @author Vincenzo K. Marcovecchio
1315  * @package admin
1316  */
1317 class parentable_part_of_admin_tree extends part_of_admin_tree {
1319     /**
1320      * Adds a part_of_admin_tree object to the admin tree.
1321      *
1322      * Used to add a part_of_admin_tree object to this object or a child of this
1323      * object. $something should only be added if $destinationname matches
1324      * $this->name. If it doesn't, add should be called on child objects that are
1325      * also parentable_part_of_admin_tree's.
1326      *
1327      * @param string $destinationname The internal name of the new parent for $something.
1328      * @param part_of_admin_tree &$something The object to be added.
1329      * @return bool True on success, false on failure.
1330      */
1331     function add($destinationname, $something) {
1332         trigger_error('Admin class does not implement method <strong>add()</strong>', E_USER_WARNING);
1333         return;
1334     }
1338 /**
1339  * The object used to represent folders (a.k.a. categories) in the admin tree block.
1340  *
1341  * Each admin_category object contains a number of part_of_admin_tree objects.
1342  *
1343  * @author Vincenzo K. Marcovecchio
1344  * @package admin
1345  */
1346 class admin_category extends parentable_part_of_admin_tree {
1348     /**
1349      * @var mixed An array of part_of_admin_tree objects that are this object's children
1350      */
1351     var $children;
1353     /**
1354      * @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1355      */
1356     var $name;
1358     /**
1359      * @var string The displayed name for this category. Usually obtained through get_string()
1360      */
1361     var $visiblename;
1363     /**
1364      * @var bool Should this category be hidden in admin tree block?
1365      */
1366     var $hidden;
1368     /**
1369      * paths
1370      */
1371     var $path;
1372     var $visiblepath;
1374     /**
1375      * Constructor for an empty admin category
1376      *
1377      * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1378      * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
1379      * @param bool $hidden hide category in admin tree block
1380      */
1381     function admin_category($name, $visiblename, $hidden=false) {
1382         $this->children    = array();
1383         $this->name        = $name;
1384         $this->visiblename = $visiblename;
1385         $this->hidden      = $hidden;
1386     }
1388     /**
1389      * Returns a reference to the part_of_admin_tree object with internal name $name.
1390      *
1391      * @param string $name The internal name of the object we want.
1392      * @param bool $findpath initialize path and visiblepath arrays
1393      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1394      */
1395     function &locate($name, $findpath=false) {
1396         if ($this->name == $name) {
1397             if ($findpath) {
1398                 $this->visiblepath[] = $this->visiblename;
1399                 $this->path[]        = $this->name;
1400             }
1401             return $this;
1402         }
1404         $return = NULL;
1405         foreach($this->children as $childid=>$unused) {
1406             if ($return =& $this->children[$childid]->locate($name, $findpath)) {
1407                 break;
1408             }
1409         }
1411         if (!is_null($return) and $findpath) {
1412             $return->visiblepath[] = $this->visiblename;
1413             $return->path[]        = $this->name;
1414         }
1416         return $return;
1417     }
1419     /**
1420      * Search using query
1421      * @param strin query
1422      * @return mixed array-object structure of found settings and pages
1423      */
1424     function search($query) {
1425         $result = array();
1426         foreach ($this->children as $child) {
1427             $subsearch = $child->search($query);
1428             if (!is_array($subsearch)) {
1429                 debugging('Incorrect search result from '.$child->name);
1430                 continue;
1431             }
1432             $result = array_merge($result, $subsearch);
1433         }
1434         return $result;
1435     }
1437     /**
1438      * Removes part_of_admin_tree object with internal name $name.
1439      *
1440      * @param string $name The internal name of the object we want to remove.
1441      * @return bool success
1442      */
1443     function prune($name) {
1445         if ($this->name == $name) {
1446             return false;  //can not remove itself
1447         }
1449         foreach($this->children as $precedence => $child) {
1450             if ($child->name == $name) {
1451                 // found it!
1452                 unset($this->children[$precedence]);
1453                 return true;
1454             }
1455             if ($this->children[$precedence]->prune($name)) {
1456                 return true;
1457             }
1458         }
1459         return false;
1460     }
1462     /**
1463      * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
1464      *
1465      * @param string $destinationame The internal name of the immediate parent that we want for $something.
1466      * @param mixed $something A part_of_admin_tree or setting instanceto be added.
1467      * @return bool True if successfully added, false if $something can not be added.
1468      */
1469     function add($parentname, $something) {
1470         $parent =& $this->locate($parentname);
1471         if (is_null($parent)) {
1472             debugging('parent does not exist!');
1473             return false;
1474         }
1476         if (is_a($something, 'part_of_admin_tree')) {
1477             if (!is_a($parent, 'parentable_part_of_admin_tree')) {
1478                 debugging('error - parts of tree can be inserted only into parentable parts');
1479                 return false;
1480             }
1481             $parent->children[] = $something;
1482             return true;
1484         } else {
1485             debugging('error - can not add this element');
1486             return false;
1487         }
1489     }
1491     /**
1492      * Checks if the user has access to anything in this category.
1493      *
1494      * @return bool True if the user has access to atleast one child in this category, false otherwise.
1495      */
1496     function check_access() {
1497         foreach ($this->children as $child) {
1498             if ($child->check_access()) {
1499                 return true;
1500             }
1501         }
1502         return false;
1503     }
1505     /**
1506      * Is this category hidden in admin tree block?
1507      *
1508      * @return bool True if hidden
1509      */
1510     function is_hidden() {
1511         return $this->hidden;
1512     }
1515 class admin_root extends admin_category {
1516     /**
1517      * list of errors
1518      */
1519     var $errors;
1521     /**
1522      * search query
1523      */
1524     var $search;
1526     /**
1527      * full tree flag - true means all settings required, false onlypages required
1528      */
1529     var $fulltree;
1532     function admin_root() {
1533         parent::admin_category('root', get_string('administration'), false);
1534         $this->errors   = array();
1535         $this->search   = '';
1536         $this->fulltree = true;
1537     }
1540 /**
1541  * Links external PHP pages into the admin tree.
1542  *
1543  * See detailed usage example at the top of this document (adminlib.php)
1544  *
1545  * @author Vincenzo K. Marcovecchio
1546  * @package admin
1547  */
1548 class admin_externalpage extends part_of_admin_tree {
1550     /**
1551      * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
1552      */
1553     var $name;
1555     /**
1556      * @var string The displayed name for this external page. Usually obtained through get_string().
1557      */
1558     var $visiblename;
1560     /**
1561      * @var string The external URL that we should link to when someone requests this external page.
1562      */
1563     var $url;
1565     /**
1566      * @var string The role capability/permission a user must have to access this external page.
1567      */
1568     var $req_capability;
1570     /**
1571      * @var object The context in which capability/permission should be checked, default is site context.
1572      */
1573     var $context;
1575     /**
1576      * @var bool hidden in admin tree block.
1577      */
1578     var $hidden;
1580     /**
1581      * visible path
1582      */
1583     var $path;
1584     var $visiblepath;
1586     /**
1587      * Constructor for adding an external page into the admin tree.
1588      *
1589      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1590      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1591      * @param string $url The external URL that we should link to when someone requests this external page.
1592      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1593      */
1594     function admin_externalpage($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1595         $this->name        = $name;
1596         $this->visiblename = $visiblename;
1597         $this->url         = $url;
1598         if (is_array($req_capability)) {
1599             $this->req_capability = $req_capability;
1600         } else {
1601             $this->req_capability = array($req_capability);
1602         }
1603         $this->hidden  = $hidden;
1604         $this->context = $context;
1605     }
1607     /**
1608      * Returns a reference to the part_of_admin_tree object with internal name $name.
1609      *
1610      * @param string $name The internal name of the object we want.
1611      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1612      */
1613     function &locate($name, $findpath=false) {
1614         if ($this->name == $name) {
1615             if ($findpath) {
1616                 $this->visiblepath = array($this->visiblename);
1617                 $this->path        = array($this->name);
1618             }
1619             return $this;
1620         } else {
1621             $return = NULL;
1622             return $return;
1623         }
1624     }
1626     function prune($name) {
1627         return false;
1628     }
1630     /**
1631      * Search using query
1632      * @param strin query
1633      * @return mixed array-object structure of found settings and pages
1634      */
1635     function search($query) {
1636         $textlib = textlib_get_instance();
1638         $found = false;
1639         if (strpos(strtolower($this->name), $query) !== false) {
1640             $found = true;
1641         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1642             $found = true;
1643         }
1644         if ($found) {
1645             $result = new object();
1646             $result->page     = $this;
1647             $result->settings = array();
1648             return array($this->name => $result);
1649         } else {
1650             return array();
1651         }
1652     }
1654     /**
1655      * Determines if the current user has access to this external page based on $this->req_capability.
1656      * @return bool True if user has access, false otherwise.
1657      */
1658     function check_access() {
1659         if (!get_site()) {
1660             return true; // no access check before site is fully set up
1661         }
1662         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
1663         foreach($this->req_capability as $cap) {
1664             if (has_capability($cap, $context)) {
1665                 return true;
1666             }
1667         }
1668         return false;
1669     }
1671     /**
1672      * Is this external page hidden in admin tree block?
1673      *
1674      * @return bool True if hidden
1675      */
1676     function is_hidden() {
1677         return $this->hidden;
1678     }
1682 /**
1683  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1684  *
1685  * @author Vincenzo K. Marcovecchio
1686  * @package admin
1687  */
1688 class admin_settingpage extends part_of_admin_tree {
1690     /**
1691      * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
1692      */
1693     var $name;
1695     /**
1696      * @var string The displayed name for this external page. Usually obtained through get_string().
1697      */
1698     var $visiblename;
1699     /**
1700      * @var mixed An array of admin_setting objects that are part of this setting page.
1701      */
1702     var $settings;
1704     /**
1705      * @var string The role capability/permission a user must have to access this external page.
1706      */
1707     var $req_capability;
1709     /**
1710      * @var object The context in which capability/permission should be checked, default is site context.
1711      */
1712     var $context;
1714     /**
1715      * @var bool hidden in admin tree block.
1716      */
1717     var $hidden;
1719     /**
1720      * paths
1721      */
1722     var $path;
1723     var $visiblepath;
1725     // see admin_externalpage
1726     function admin_settingpage($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1727         $this->settings    = new object();
1728         $this->name        = $name;
1729         $this->visiblename = $visiblename;
1730         if (is_array($req_capability)) {
1731             $this->req_capability = $req_capability;
1732         } else {
1733             $this->req_capability = array($req_capability);
1734         }
1735         $this->hidden      = $hidden;
1736         $this->context     = $context;
1737     }
1739     // see admin_category
1740     function &locate($name, $findpath=false) {
1741         if ($this->name == $name) {
1742             if ($findpath) {
1743                 $this->visiblepath = array($this->visiblename);
1744                 $this->path        = array($this->name);
1745             }
1746             return $this;
1747         } else {
1748             $return = NULL;
1749             return $return;
1750         }
1751     }
1753     function search($query) {
1754         $found = array();
1756         foreach ($this->settings as $setting) {
1757             if ($setting->is_related($query)) {
1758                 $found[] = $setting;
1759             }
1760         }
1762         if ($found) {
1763             $result = new object();
1764             $result->page     = $this;
1765             $result->settings = $found;
1766             return array($this->name => $result);
1767         }
1769         $textlib = textlib_get_instance();
1771         $found = false;
1772         if (strpos(strtolower($this->name), $query) !== false) {
1773             $found = true;
1774         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1775             $found = true;
1776         }
1777         if ($found) {
1778             $result = new object();
1779             $result->page     = $this;
1780             $result->settings = array();
1781             return array($this->name => $result);
1782         } else {
1783             return array();
1784         }
1785     }
1787     function prune($name) {
1788         return false;
1789     }
1791     /**
1792      * 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
1793      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1794      * @param object $setting is the admin_setting object you want to add
1795      * @return true if successful, false if not
1796      */
1797     function add($setting) {
1798         if (!is_a($setting, 'admin_setting')) {
1799             debugging('error - not a setting instance');
1800             return false;
1801         }
1803         $this->settings->{$setting->name} = $setting;
1804         return true;
1805     }
1807     // see admin_externalpage
1808     function check_access() {
1809         if (!get_site()) {
1810             return true; // no access check before site is fully set up
1811         }
1812         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
1813         foreach($this->req_capability as $cap) {
1814             if (has_capability($cap, $context)) {
1815                 return true;
1816             }
1817         }
1818         return false;
1819     }
1821     /**
1822      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1823      * returns a string of the html
1824      */
1825     function output_html() {
1826         $adminroot =& admin_get_root();
1827         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1828         foreach($this->settings as $setting) {
1829             $fullname = $setting->get_full_name();
1830             if (array_key_exists($fullname, $adminroot->errors)) {
1831                 $data = $adminroot->errors[$fullname]->data;
1832             } else {
1833                 $data = $setting->get_setting();
1834                 // do not use defaults if settings not available - upgrdesettings handles the defaults!
1835             }
1836             $return .= $setting->output_html($data);
1837         }
1838         $return .= '</fieldset>';
1839         return $return;
1840     }
1842     /**
1843      * Is this settigns page hidden in admin tree block?
1844      *
1845      * @return bool True if hidden
1846      */
1847     function is_hidden() {
1848         return $this->hidden;
1849     }
1854 /**
1855  * Admin settings class. Only exists on setting pages.
1856  * Read & write happens at this level; no authentication.
1857  */
1858 class admin_setting {
1860     var $name;
1861     var $visiblename;
1862     var $description;
1863     var $defaultsetting;
1864     var $updatedcallback;
1865     var $plugin; // null means main config table
1867     /**
1868      * Constructor
1869      * @param $name string unique ascii name
1870      * @param $visiblename string localised name
1871      * @param strin $description localised long description
1872      * @param mixed $defaultsetting string or array depending on implementation
1873      */
1874     function admin_setting($name, $visiblename, $description, $defaultsetting) {
1875         $this->name           = $name;
1876         $this->visiblename    = $visiblename;
1877         $this->description    = $description;
1878         $this->defaultsetting = $defaultsetting;
1879     }
1881     function get_full_name() {
1882         return 's_'.$this->plugin.'_'.$this->name;
1883     }
1885     function get_id() {
1886         return 'id_s_'.$this->plugin.'_'.$this->name;
1887     }
1889     function config_read($name) {
1890         global $CFG;
1891         if ($this->plugin === 'backup') {
1892             require_once($CFG->dirroot.'/backup/lib.php');
1893             $backupconfig = backup_get_config();
1894             if (isset($backupconfig->$name)) {
1895                 return $backupconfig->$name;
1896             } else {
1897                 return NULL;
1898             }
1900         } else if (!empty($this->plugin)) {
1901             $value = get_config($this->plugin, $name);
1902             return $value === false ? NULL : $value;
1904         } else {
1905             if (isset($CFG->$name)) {
1906                 return $CFG->$name;
1907             } else {
1908                 return NULL;
1909             }
1910         }
1911     }
1913     function config_write($name, $value) {
1914         global $CFG;
1915         if ($this->plugin === 'backup') {
1916             require_once($CFG->dirroot.'/backup/lib.php');
1917             return (boolean)backup_set_config($name, $value);
1918         } else {
1919             return (boolean)set_config($name, $value, $this->plugin);
1920         }
1921     }
1923     /**
1924      * Returns current value of this setting
1925      * @return mixed array or string depending on instance, NULL means not set yet
1926      */
1927     function get_setting() {
1928         // has to be overridden
1929         return NULL;
1930     }
1932     /**
1933      * Returns default setting if exists
1934      * @return mixed array or string depending on instance; NULL means no default, user must supply
1935      */
1936     function get_defaultsetting() {
1937         return $this->defaultsetting;
1938     }
1940     /**
1941      * Store new setting
1942      * @param mixed string or array, must not be NULL
1943      * @return '' if ok, string error message otherwise
1944      */
1945     function write_setting($data) {
1946         // should be overridden
1947         return '';
1948     }
1950     /**
1951      * Return part of form with setting
1952      * @param mixed data array or string depending on setting
1953      * @return string
1954      */
1955     function output_html($data, $query='') {
1956         // should be overridden
1957         return;
1958     }
1960     /**
1961      * function called if setting updated - cleanup, cache reset, etc.
1962      */
1963     function set_updatedcallback($functionname) {
1964         $this->updatedcallback = $functionname;
1965     }
1967     /**
1968      * Is setting related to query text - used when searching
1969      * @param string $query
1970      * @return bool
1971      */
1972     function is_related($query) {
1973         if (strpos(strtolower($this->name), $query) !== false) {
1974             return true;
1975         }
1976         $textlib = textlib_get_instance();
1977         if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1978             return true;
1979         }
1980         if (strpos($textlib->strtolower($this->description), $query) !== false) {
1981             return true;
1982         }
1983         $current = $this->get_setting();
1984         if (!is_null($current)) {
1985             if (is_string($current)) {
1986                 if (strpos($textlib->strtolower($current), $query) !== false) {
1987                     return true;
1988                 }
1989             }
1990         }
1991         $default = $this->get_defaultsetting();
1992         if (!is_null($default)) {
1993             if (is_string($default)) {
1994                 if (strpos($textlib->strtolower($default), $query) !== false) {
1995                     return true;
1996                 }
1997             }
1998         }
1999         return false;
2000     }
2003 /**
2004  * No setting - just heading and text.
2005  */
2006 class admin_setting_heading extends admin_setting {
2007     /**
2008      * not a setting, just text
2009      * @param string $name of setting
2010      * @param string $heading heading
2011      * @param string $information text in box
2012      */
2013     function admin_setting_heading($name, $heading, $information) {
2014         parent::admin_setting($name, $heading, $information, '');
2015     }
2017     function get_setting() {
2018         return true;
2019     }
2021     function get_defaultsetting() {
2022         return true;
2023     }
2025     function write_setting($data) {
2026         // do not write any setting
2027         return '';
2028     }
2030     function output_html($data, $query='') {
2031         $return = '';
2032         if ($this->visiblename != '') {
2033             $return .= print_heading('<a name="'.$this->name.'">'.highlightfast($query, $this->visiblename).'</a>', '', 3, 'main', true);
2034         }
2035         if ($this->description != '') {
2036             $return .= print_box(highlight($query, $this->description), 'generalbox formsettingheading', '', true);
2037         }
2038         return $return;
2039     }
2042 /**
2043  * The most flexibly setting, user is typing text
2044  */
2045 class admin_setting_configtext extends admin_setting {
2047     var $paramtype;
2048     var $size;
2050     /**
2051      * config text contructor
2052      * @param string $name of setting
2053      * @param string $visiblename localised
2054      * @param string $description long localised info
2055      * @param string $defaultsetting
2056      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2057      * @param int $size default field size
2058      */
2059     function admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2060         $this->paramtype = $paramtype;
2061         if (!is_null($size)) {
2062             $this->size  = $size;
2063         } else {
2064             $this->size  = ($paramtype == PARAM_INT) ? 5 : 30;
2065         }
2066         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2067     }
2069     function get_setting() {
2070         return $this->config_read($this->name);
2071     }
2073     function write_setting($data) {
2074         if ($this->paramtype === PARAM_INT and $data === '') {
2075             // do not complain if '' used instead of 0
2076             $data = 0;
2077         }
2078         // $data is a string
2079         $validated = $this->validate($data);
2080         if ($validated !== true) {
2081             return $validated;
2082         }
2083         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2084     }
2086     /**
2087      * Validate data before storage
2088      * @param string data
2089      * @return mixed true if ok string if error found
2090      */
2091     function validate($data) {
2092         if (is_string($this->paramtype)) {
2093             if (preg_match($this->paramtype, $data)) {
2094                 return true;
2095             } else {
2096                 return get_string('validateerror', 'admin');
2097             }
2099         } else if ($this->paramtype === PARAM_RAW) {
2100             return true;
2102         } else {
2103             $cleaned = clean_param($data, $this->paramtype);
2104             if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
2105                 return true;
2106             } else {
2107                 return get_string('validateerror', 'admin');
2108             }
2109         }
2110     }
2112     function output_html($data, $query='') {
2113         $default = $this->get_defaultsetting();
2115         return format_admin_setting($this, $this->visiblename,
2116                 '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
2117                 $this->description, true, '', $default, $query);
2118     }
2121 /**
2122  * General text area without html editor.
2123  */
2124 class admin_setting_configtextarea extends admin_setting_configtext {
2125     var $rows;
2126     var $cols;
2128     function admin_setting_configtextarea($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2129         $this->rows = $rows;
2130         $this->cols = $cols;
2131         parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype);
2132     }
2134     function output_html($data, $query='') {
2135         $default = $this->get_defaultsetting();
2137         $defaultinfo = $default;
2138         if (!is_null($default) and $default !== '') {
2139             $defaultinfo = "\n".$default;
2140         }
2142         return format_admin_setting($this, $this->visiblename,
2143                 '<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>',
2144                 $this->description, true, '', $defaultinfo, $query);
2145     }
2148 /**
2149  * Password field, allows unmasking of password
2150  */
2151 class admin_setting_configpasswordunmask extends admin_setting_configtext {
2152     /**
2153      * Constructor
2154      * @param string $name of setting
2155      * @param string $visiblename localised
2156      * @param string $description long localised info
2157      * @param string $defaultsetting default password
2158      */
2159     function admin_setting_configpasswordunmask($name, $visiblename, $description, $defaultsetting) {
2160         parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2161     }
2163     function output_html($data, $query='') {
2164         $id = $this->get_id();
2165         $unmask = get_string('unmaskpassword', 'form');
2166         $unmaskjs = '<script type="text/javascript">
2167 //<![CDATA[
2168 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
2170 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
2172 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
2174 var unmaskchb = document.createElement("input");
2175 unmaskchb.setAttribute("type", "checkbox");
2176 unmaskchb.setAttribute("id", "'.$id.'unmask");
2177 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
2178 unmaskdiv.appendChild(unmaskchb);
2180 var unmasklbl = document.createElement("label");
2181 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
2182 if (is_ie) {
2183   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
2184 } else {
2185   unmasklbl.setAttribute("for", "'.$id.'unmask");
2187 unmaskdiv.appendChild(unmasklbl);
2189 if (is_ie) {
2190   // ugly hack to work around the famous onchange IE bug
2191   unmaskchb.onclick = function() {this.blur();};
2192   unmaskdiv.onclick = function() {this.blur();};
2194 //]]>
2195 </script>';
2196         return format_admin_setting($this, $this->visiblename,
2197                 '<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>',
2198                 $this->description, true, '', NULL, $query);
2199     }
2202 /**
2203  * Path to directory
2204  */
2205 class admin_setting_configfile extends admin_setting_configtext {
2206     /**
2207      * Constructor
2208      * @param string $name of setting
2209      * @param string $visiblename localised
2210      * @param string $description long localised info
2211      * @param string $defaultdirectory default directory location
2212      */
2213     function admin_setting_configfile($name, $visiblename, $description, $defaultdirectory) {
2214         parent::admin_setting_configtext($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2215     }
2217     function output_html($data, $query='') {
2218         $default = $this->get_defaultsetting();
2220         if ($data) {
2221             if (file_exists($data)) {
2222                 $executable = '<span class="pathok">&#x2714;</span>';
2223             } else {
2224                 $executable = '<span class="patherror">&#x2718;</span>';
2225             }
2226         } else {
2227             $executable = '';
2228         }
2230         return format_admin_setting($this, $this->visiblename,
2231                 '<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>',
2232                 $this->description, true, '', $default, $query);
2233     }
2236 /**
2237  * Path to executable file
2238  */
2239 class admin_setting_configexecutable extends admin_setting_configfile {
2241     function output_html($data, $query='') {
2242         $default = $this->get_defaultsetting();
2244         if ($data) {
2245             if (file_exists($data) and is_executable($data)) {
2246                 $executable = '<span class="pathok">&#x2714;</span>';
2247             } else {
2248                 $executable = '<span class="patherror">&#x2718;</span>';
2249             }
2250         } else {
2251             $executable = '';
2252         }
2254         return format_admin_setting($this, $this->visiblename,
2255                 '<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>',
2256                 $this->description, true, '', $default, $query);
2257     }
2260 /**
2261  * Path to directory
2262  */
2263 class admin_setting_configdirectory extends admin_setting_configfile {
2264     function output_html($data, $query='') {
2265         $default = $this->get_defaultsetting();
2267         if ($data) {
2268             if (file_exists($data) and is_dir($data)) {
2269                 $executable = '<span class="pathok">&#x2714;</span>';
2270             } else {
2271                 $executable = '<span class="patherror">&#x2718;</span>';
2272             }
2273         } else {
2274             $executable = '';
2275         }
2277         return format_admin_setting($this, $this->visiblename,
2278                 '<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>',
2279                 $this->description, true, '', $default, $query);
2280     }
2283 /**
2284  * Checkbox
2285  */
2286 class admin_setting_configcheckbox extends admin_setting {
2287     var $yes;
2288     var $no;
2290     /**
2291      * Constructor
2292      * @param string $name of setting
2293      * @param string $visiblename localised
2294      * @param string $description long localised info
2295      * @param string $defaultsetting
2296      * @param string $yes value used when checked
2297      * @param string $no value used when not checked
2298      */
2299     function admin_setting_configcheckbox($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2300         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2301         $this->yes = (string)$yes;
2302         $this->no  = (string)$no;
2303     }
2305     function get_setting() {
2306         return $this->config_read($this->name);
2307     }
2309     function write_setting($data) {
2310         if ((string)$data === $this->yes) { // convert to strings before comparison
2311             $data = $this->yes;
2312         } else {
2313             $data = $this->no;
2314         }
2315         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2316     }
2318     function output_html($data, $query='') {
2319         $default = $this->get_defaultsetting();
2321         if (!is_null($default)) {
2322             if ((string)$default === $this->yes) {
2323                 $defaultinfo = get_string('checkboxyes', 'admin');
2324             } else {
2325                 $defaultinfo = get_string('checkboxno', 'admin');
2326             }
2327         } else {
2328             $defaultinfo = NULL;
2329         }
2331         if ((string)$data === $this->yes) { // convert to strings before comparison
2332             $checked = 'checked="checked"';
2333         } else {
2334             $checked = '';
2335         }
2337         return format_admin_setting($this, $this->visiblename,
2338                 '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2339                 .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2340                 $this->description, true, '', $defaultinfo, $query);
2341     }
2344 /**
2345  * Multiple checkboxes, each represents different value, stored in csv format
2346  */
2347 class admin_setting_configmulticheckbox extends admin_setting {
2348     var $choices;
2350     /**
2351      * Constructor
2352      * @param string $name of setting
2353      * @param string $visiblename localised
2354      * @param string $description long localised info
2355      * @param array $defaultsetting array of selected
2356      * @param array $choices array of $value=>$label for each checkbox
2357      */
2358     function admin_setting_configmulticheckbox($name, $visiblename, $description, $defaultsetting, $choices) {
2359         $this->choices = $choices;
2360         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2361     }
2363     /**
2364      * This function may be used in ancestors for lazy loading of choices
2365      * @return true if loaded, false if error
2366      */
2367     function load_choices() {
2368         /*
2369         if (is_array($this->choices)) {
2370             return true;
2371         }
2372         .... load choices here
2373         */
2374         return true;
2375     }
2377     /**
2378      * Is setting related to query text - used when searching
2379      * @param string $query
2380      * @return bool
2381      */
2382     function is_related($query) {
2383         if (!$this->load_choices() or empty($this->choices)) {
2384             return false;
2385         }
2386         if (parent::is_related($query)) {
2387             return true;
2388         }
2390         $textlib = textlib_get_instance();
2391         foreach ($this->choices as $desc) {
2392             if (strpos($textlib->strtolower($desc), $query) !== false) {
2393                 return true;
2394             }
2395         }
2396         return false;
2397     }
2399     function get_setting() {
2400         $result = $this->config_read($this->name);
2402         if (is_null($result)) {
2403             return NULL;
2404         }
2405         if ($result === '') {
2406             return array();
2407         }
2408         $enabled = explode(',', $result);
2409         $setting = array();
2410         foreach ($enabled as $option) {
2411             $setting[$option] = 1;
2412         }
2413         return $setting;
2414     }
2416     function write_setting($data) {
2417         if (!is_array($data)) {
2418             return ''; // ignore it
2419         }
2420         if (!$this->load_choices() or empty($this->choices)) {
2421             return '';
2422         }
2423         unset($data['xxxxx']);
2424         $result = array();
2425         foreach ($data as $key => $value) {
2426             if ($value and array_key_exists($key, $this->choices)) {
2427                 $result[] = $key;
2428             }
2429         }
2430         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2431     }
2433     function output_html($data, $query='') {
2434         if (!$this->load_choices() or empty($this->choices)) {
2435             return '';
2436         }
2437         $default = $this->get_defaultsetting();
2438         if (is_null($default)) {
2439             $default = array();
2440         }
2441         if (is_null($data)) {
2442             $data = array();
2443         }
2444         $options = array();
2445         $defaults = array();
2446         foreach ($this->choices as $key=>$description) {
2447             if (!empty($data[$key])) {
2448                 $checked = 'checked="checked"';
2449             } else {
2450                 $checked = '';
2451             }
2452             if (!empty($default[$key])) {
2453                 $defaults[] = $description;
2454             }
2456             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
2457                          .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
2458         }
2460         if (is_null($default)) {
2461             $defaultinfo = NULL;
2462         } else if (!empty($defaults)) {
2463             $defaultinfo = implode(', ', $defaults);
2464         } else {
2465             $defaultinfo = get_string('none');
2466         }
2468         $return = '<div class="form-multicheckbox">';
2469         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2470         if ($options) {
2471             $return .= '<ul>';
2472             foreach ($options as $option) {
2473                 $return .= '<li>'.$option.'</li>';
2474             }
2475             $return .= '</ul>';
2476         }
2477         $return .= '</div>';
2479         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2481     }
2484 /**
2485  * Multiple checkboxes 2, value stored as string 00101011
2486  */
2487 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
2488     function get_setting() {
2489         $result = $this->config_read($this->name);
2490         if (is_null($result)) {
2491             return NULL;
2492         }
2493         if (!$this->load_choices()) {
2494             return NULL;
2495         }
2496         $result = str_pad($result, count($this->choices), '0');
2497         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2498         $setting = array();
2499         foreach ($this->choices as $key=>$unused) {
2500             $value = array_shift($result);
2501             if ($value) {
2502                 $setting[$key] = 1;
2503             }
2504         }
2505         return $setting;
2506     }
2508     function write_setting($data) {
2509         if (!is_array($data)) {
2510             return ''; // ignore it
2511         }
2512         if (!$this->load_choices() or empty($this->choices)) {
2513             return '';
2514         }
2515         $result = '';
2516         foreach ($this->choices as $key=>$unused) {
2517             if (!empty($data[$key])) {
2518                 $result .= '1';
2519             } else {
2520                 $result .= '0';
2521             }
2522         }
2523         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2524     }
2527 /**
2528  * Select one value from list
2529  */
2530 class admin_setting_configselect extends admin_setting {
2531     var $choices;
2533     /**
2534      * Constructor
2535      * @param string $name of setting
2536      * @param string $visiblename localised
2537      * @param string $description long localised info
2538      * @param string $defaultsetting
2539      * @param array $choices array of $value=>$label for each selection
2540      */
2541     function admin_setting_configselect($name, $visiblename, $description, $defaultsetting, $choices) {
2542         $this->choices = $choices;
2543         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2544     }
2546     /**
2547      * This function may be used in ancestors for lazy loading of choices
2548      * @return true if loaded, false if error
2549      */
2550     function load_choices() {
2551         /*
2552         if (is_array($this->choices)) {
2553             return true;
2554         }
2555         .... load choices here
2556         */
2557         return true;
2558     }
2560     function is_related($query) {
2561         if (parent::is_related($query)) {
2562             return true;
2563         }
2564         if (!$this->load_choices()) {
2565             return false;
2566         }
2567         $textlib = textlib_get_instance();
2568         foreach ($this->choices as $key=>$value) {
2569             if (strpos($textlib->strtolower($key), $query) !== false) {
2570                 return true;
2571             }
2572             if (strpos($textlib->strtolower($value), $query) !== false) {
2573                 return true;
2574             }
2575         }
2576         return false;
2577     }
2579     function get_setting() {
2580         return $this->config_read($this->name);
2581     }
2583     function write_setting($data) {
2584         if (!$this->load_choices() or empty($this->choices)) {
2585             return '';
2586         }
2587         if (!array_key_exists($data, $this->choices)) {
2588             return ''; // ignore it
2589         }
2591         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2592     }
2594     /**
2595      * Ensure the options are loaded, and generate the HTML for the select
2596      * element and any warning message. Separating this out from output_html
2597      * makes it easier to subclass this class.
2598      *
2599      * @param string $data the option to show as selected.
2600      * @param string $current the currently selected option in the database, null if none.
2601      * @param string $default the default selected option.
2602      * @return array the HTML for the select element, and a warning message.
2603      */
2604     function output_select_html($data, $current, $default, $extraname = '') {
2605         if (!$this->load_choices() or empty($this->choices)) {
2606             return array('', '');
2607         }
2609         $warning = '';
2610         if (is_null($current)) {
2611             // first run
2612         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2613             // no warning
2614         } else if (!array_key_exists($current, $this->choices)) {
2615             $warning = get_string('warningcurrentsetting', 'admin', s($current));
2616             if (!is_null($default) and $data == $current) {
2617                 $data = $default; // use default instead of first value when showing the form
2618             }
2619         }
2621         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
2622         foreach ($this->choices as $key => $value) {
2623             // the string cast is needed because key may be integer - 0 is equal to most strings!
2624             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
2625         }
2626         $selecthtml .= '</select>';
2627         return array($selecthtml, $warning);
2628     }
2630     function output_html($data, $query='') {
2631         $default = $this->get_defaultsetting();
2632         $current = $this->get_setting();
2634         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2635         if (!$selecthtml) {
2636             return '';
2637         }
2639         if (!is_null($default) and array_key_exists($default, $this->choices)) {
2640             $defaultinfo = $this->choices[$default];
2641         } else {
2642             $defaultinfo = NULL;
2643         }
2645         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
2647         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
2648     }
2651 /**
2652  * Select multiple items from list
2653  */
2654 class admin_setting_configmultiselect extends admin_setting_configselect {
2655     /**
2656      * Constructor
2657      * @param string $name of setting
2658      * @param string $visiblename localised
2659      * @param string $description long localised info
2660      * @param array $defaultsetting array of selected items
2661      * @param array $choices array of $value=>$label for each list item
2662      */
2663     function admin_setting_configmultiselect($name, $visiblename, $description, $defaultsetting, $choices) {
2664         parent::admin_setting_configselect($name, $visiblename, $description, $defaultsetting, $choices);
2665     }
2667     function get_setting() {
2668         $result = $this->config_read($this->name);
2669         if (is_null($result)) {
2670             return NULL;
2671         }
2672         if ($result === '') {
2673             return array();
2674         }
2675         return explode(',', $result);
2676     }
2678     function write_setting($data) {
2679         if (!is_array($data)) {
2680             return ''; //ignore it
2681         }
2682         if (!$this->load_choices() or empty($this->choices)) {
2683             return '';
2684         }
2686         unset($data['xxxxx']);
2688         $save = array();
2689         foreach ($data as $value) {
2690             if (!array_key_exists($value, $this->choices)) {
2691                 continue; // ignore it
2692             }
2693             $save[] = $value;
2694         }
2696         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2697     }
2699     /**
2700      * Is setting related to query text - used when searching
2701      * @param string $query
2702      * @return bool
2703      */
2704     function is_related($query) {
2705         if (!$this->load_choices() or empty($this->choices)) {
2706             return false;
2707         }
2708         if (parent::is_related($query)) {
2709             return true;
2710         }
2712         $textlib = textlib_get_instance();
2713         foreach ($this->choices as $desc) {
2714             if (strpos($textlib->strtolower($desc), $query) !== false) {
2715                 return true;
2716             }
2717         }
2718         return false;
2719     }
2721     function output_html($data, $query='') {
2722         if (!$this->load_choices() or empty($this->choices)) {
2723             return '';
2724         }
2725         $choices = $this->choices;
2726         $default = $this->get_defaultsetting();
2727         if (is_null($default)) {
2728             $default = array();
2729         }
2730         if (is_null($data)) {
2731             $data = array();
2732         }
2734         $defaults = array();
2735         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2736         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="10" multiple="multiple">';
2737         foreach ($this->choices as $key => $description) {
2738             if (in_array($key, $data)) {
2739                 $selected = 'selected="selected"';
2740             } else {
2741                 $selected = '';
2742             }
2743             if (in_array($key, $default)) {
2744                 $defaults[] = $description;
2745             }
2747             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2748         }
2750         if (is_null($default)) {
2751             $defaultinfo = NULL;
2752         } if (!empty($defaults)) {
2753             $defaultinfo = implode(', ', $defaults);
2754         } else {
2755             $defaultinfo = get_string('none');
2756         }
2758         $return .= '</select></div>';
2759         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
2760     }
2763 /**
2764  * Time selector
2765  * this is a liiitle bit messy. we're using two selects, but we're returning
2766  * them as an array named after $name (so we only use $name2 internally for the setting)
2767  */
2768 class admin_setting_configtime extends admin_setting {
2769     var $name2;
2771     /**
2772      * Constructor
2773      * @param string $hoursname setting for hours
2774      * @param string $minutesname setting for hours
2775      * @param string $visiblename localised
2776      * @param string $description long localised info
2777      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2778      */
2779     function admin_setting_configtime($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
2780         $this->name2 = $minutesname;
2781         parent::admin_setting($hoursname, $visiblename, $description, $defaultsetting);
2782     }
2784     function get_setting() {
2785         $result1 = $this->config_read($this->name);
2786         $result2 = $this->config_read($this->name2);
2787         if (is_null($result1) or is_null($result2)) {
2788             return NULL;
2789         }
2791         return array('h' => $result1, 'm' => $result2);
2792     }
2794     function write_setting($data) {
2795         if (!is_array($data)) {
2796             return '';
2797         }
2799         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2800         return ($result ? '' : get_string('errorsetting', 'admin'));
2801     }
2803     function output_html($data, $query='') {
2804         $default = $this->get_defaultsetting();
2806         if (is_array($default)) {
2807             $defaultinfo = $default['h'].':'.$default['m'];
2808         } else {
2809             $defaultinfo = NULL;
2810         }
2812         $return = '<div class="form-time defaultsnext">'.
2813                   '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
2814         for ($i = 0; $i < 24; $i++) {
2815             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2816         }
2817         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2818         for ($i = 0; $i < 60; $i += 5) {
2819             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2820         }
2821         $return .= '</select></div>';
2822         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2823     }
2827 class admin_setting_configiplist extends admin_setting_configtextarea {
2828     function validate($data) {
2829         if(!empty($data)) {
2830             $ips = explode("\n", $data);
2831         } else {
2832             return true;
2833         }
2834         $result = true;
2835         foreach($ips as $ip) {
2836             $ip = trim($ip);
2837             if(preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
2838                    preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
2839                    preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
2840                 $result = true;
2841             } else {
2842                 $result = false;
2843                 break;
2844             }
2845         }
2846         if($result){
2847             return true;
2848         } else {
2849             return get_string('validateerror', 'admin');
2850         }
2851     }
2854 /**
2855  * Special checkbox for calendar - resets SESSION vars.
2856  */
2857 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
2858     function admin_setting_special_adminseesall() {
2859         parent::admin_setting_configcheckbox('calendar_adminseesall', get_string('adminseesall', 'admin'),
2860                                              get_string('helpadminseesall', 'admin'), '0');
2861     }
2863     function write_setting($data) {
2864         global $SESSION;
2865         unset($SESSION->cal_courses_shown);
2866         return parent::write_setting($data);
2867     }
2870 /**
2871  * Special select for settings that are altered in setup.php and can not be altered on the fly
2872  */
2873 class admin_setting_special_selectsetup extends admin_setting_configselect {
2874     function get_setting() {
2875         // read directly from db!
2876         return get_config(NULL, $this->name);
2877     }
2879     function write_setting($data) {
2880         global $CFG;
2881         // do not change active CFG setting!
2882         $current = $CFG->{$this->name};
2883         $result = parent::write_setting($data);
2884         $CFG->{$this->name} = $current;
2885         return $result;
2886     }
2889 /**
2890  * Special select for frontpage - stores data in course table
2891  */
2892 class admin_setting_sitesetselect extends admin_setting_configselect {
2893     function get_setting() {
2894         $site = get_site();
2895         return $site->{$this->name};
2896     }
2898     function write_setting($data) {
2899         global $DB;
2900         if (!in_array($data, array_keys($this->choices))) {
2901             return get_string('errorsetting', 'admin');
2902         }
2903         $record = new stdClass();
2904         $record->id           = SITEID;
2905         $temp                 = $this->name;
2906         $record->$temp        = $data;
2907         $record->timemodified = time();
2908         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
2909     }
2912 /**
2913  * Special select - lists on the frontpage - hacky
2914  */
2915 class admin_setting_courselist_frontpage extends admin_setting {
2916     var $choices;
2918     function admin_setting_courselist_frontpage($loggedin) {
2919         global $CFG;
2920         require_once($CFG->dirroot.'/course/lib.php');
2921         $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
2922         $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
2923         $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
2924         $defaults    = array(FRONTPAGECOURSELIST);
2925         parent::admin_setting($name, $visiblename, $description, $defaults);
2926     }
2928     function load_choices() {
2929         global $DB;
2930         if (is_array($this->choices)) {
2931             return true;
2932         }
2933         $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
2934                                FRONTPAGECOURSELIST    => get_string('frontpagecourselist'),
2935                                FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
2936                                FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
2937                                'none'                 => get_string('none'));
2938         if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
2939             unset($this->choices[FRONTPAGECOURSELIST]);
2940         }
2941         return true;
2942     }
2943     function get_setting() {
2944         $result = $this->config_read($this->name);
2945         if (is_null($result)) {
2946             return NULL;
2947         }
2948         if ($result === '') {
2949             return array();
2950         }
2951         return explode(',', $result);
2952     }
2954     function write_setting($data) {
2955         if (!is_array($data)) {
2956             return '';
2957         }
2958         $this->load_choices();
2959         $save = array();
2960         foreach($data as $datum) {
2961             if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
2962                 continue;
2963             }
2964             $save[$datum] = $datum; // no duplicates
2965         }
2966         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2967     }
2969     function output_html($data, $query='') {
2970         $this->load_choices();
2971         $currentsetting = array();
2972         foreach ($data as $key) {
2973             if ($key != 'none' and array_key_exists($key, $this->choices)) {
2974                 $currentsetting[] = $key; // already selected first
2975             }
2976         }
2978         $return = '<div class="form-group">';
2979         for ($i = 0; $i < count($this->choices) - 1; $i++) {
2980             if (!array_key_exists($i, $currentsetting)) {
2981                 $currentsetting[$i] = 'none'; //none
2982             }
2983             $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
2984             foreach ($this->choices as $key => $value) {
2985                 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
2986             }
2987             $return .= '</select>';
2988             if ($i !== count($this->choices) - 2) {
2989                 $return .= '<br />';
2990             }
2991         }
2992         $return .= '</div>';
2994         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
2995     }
2998 /**
2999  * Special checkbox for frontpage - stores data in course table
3000  */
3001 class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
3002     function get_setting() {
3003         $site = get_site();
3004         return $site->{$this->name};
3005     }
3007     function write_setting($data) {
3008         global $DB;
3009         $record = new object();
3010         $record->id            = SITEID;
3011         $record->{$this->name} = ($data == '1' ? 1 : 0);
3012         $record->timemodified  = time();
3013         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3014     }
3017 /**
3018  * Special text for frontpage - stores data in course table.
3019  * Empty string means not set here. Manual setting is required.
3020  */
3021 class admin_setting_sitesettext extends admin_setting_configtext {
3022     function get_setting() {
3023         $site = get_site();
3024         return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
3025     }
3027     function validate($data) {
3028         $cleaned = clean_param($data, PARAM_MULTILANG);
3029         if ($cleaned === '') {
3030             return get_string('required');
3031         }
3032         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
3033             return true;
3034         } else {
3035             return get_string('validateerror', 'admin');
3036         }
3037     }
3039     function write_setting($data) {
3040         global $DB;
3041         $data = trim($data);
3042         $validated = $this->validate($data);
3043         if ($validated !== true) {
3044             return $validated;
3045         }
3047         $record = new object();
3048         $record->id            = SITEID;
3049         $record->{$this->name} = $data;
3050         $record->timemodified  = time();
3051         return ($DB->update_record('course', $record) ? '' : get_string('dbupdatefailed', 'error'));
3052     }
3055 /**
3056  * Special text editor for site description.
3057  */
3058 class admin_setting_special_frontpagedesc extends admin_setting {
3059     function admin_setting_special_frontpagedesc() {
3060         parent::admin_setting('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
3061     }
3063     function get_setting() {
3064         $site = get_site();
3065         return $site->{$this->name};
3066     }
3068     function write_setting($data) {
3069         global $DB;
3070         $record = new object();
3071         $record->id            = SITEID;
3072         $record->{$this->name} = $data;
3073         $record->timemodified  = time();
3074         return($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3075     }
3077     function output_html($data, $query='') {
3078         global $CFG;
3080         $CFG->adminusehtmleditor = can_use_html_editor();
3081         $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
3083         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3084     }
3087 class admin_setting_special_editorfontlist extends admin_setting {
3089     var $items;
3091     function admin_setting_special_editorfontlist() {
3092         global $CFG;
3093         $name = 'editorfontlist';
3094         $visiblename = get_string('editorfontlist', 'admin');
3095         $description = get_string('configeditorfontlist', 'admin');
3096         $defaults = array('k0' => 'Trebuchet',
3097                           'v0' => 'Trebuchet MS,Verdana,Arial,Helvetica,sans-serif',
3098                           'k1' => 'Arial',
3099                           'v1' => 'arial,helvetica,sans-serif',
3100                           'k2' => 'Courier New',
3101                           'v2' => 'courier new,courier,monospace',
3102                           'k3' => 'Georgia',
3103                           'v3' => 'georgia,times new roman,times,serif',
3104                           'k4' => 'Tahoma',
3105                           'v4' => 'tahoma,arial,helvetica,sans-serif',
3106                           'k5' => 'Times New Roman',
3107                           'v5' => 'times new roman,times,serif',
3108                           'k6' => 'Verdana',
3109                           'v6' => 'verdana,arial,helvetica,sans-serif',
3110                           'k7' => 'Impact',
3111                           'v7' => 'impact',
3112                           'k8' => 'Wingdings',
3113                           'v8' => 'wingdings');
3114         parent::admin_setting($name, $visiblename, $description, $defaults);
3115     }
3117     function get_setting() {
3118         global $CFG;
3119         $result = $this->config_read($this->name);
3120         if (is_null($result)) {
3121             return NULL;
3122         }
3123         $i = 0;
3124         $currentsetting = array();
3125         $items = explode(';', $result);
3126         foreach ($items as $item) {
3127           $item = explode(':', $item);
3128           $currentsetting['k'.$i] = $item[0];
3129           $currentsetting['v'.$i] = $item[1];
3130           $i++;
3131         }
3132         return $currentsetting;
3133     }
3135     function write_setting($data) {
3137         // there miiight be an easier way to do this :)
3138         // if this is changed, make sure the $defaults array above is modified so that this
3139         // function processes it correctly
3141         $keys = array();
3142         $values = array();
3144         foreach ($data as $key => $value) {
3145             if (substr($key,0,1) == 'k') {
3146                 $keys[substr($key,1)] = $value;
3147             } elseif (substr($key,0,1) == 'v') {
3148                 $values[substr($key,1)] = $value;
3149             }
3150         }
3152         $result = array();
3153         for ($i = 0; $i < count($keys); $i++) {
3154             if (($keys[$i] !== '') && ($values[$i] !== '')) {
3155                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).':'.clean_param($values[$i], PARAM_NOTAGS);
3156             }
3157         }
3159         return ($this->config_write($this->name, implode(';', $result)) ? '' : get_string('errorsetting', 'admin'));
3160     }
3162     function output_html($data, $query='') {
3163         $fullname = $this->get_full_name();
3164         $return = '<div class="form-group">';
3165         for ($i = 0; $i < count($data) / 2; $i++) {
3166             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
3167             $return .= '&nbsp;&nbsp;';
3168             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
3169         }
3170         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
3171         $return .= '&nbsp;&nbsp;';
3172         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3173         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
3174         $return .= '&nbsp;&nbsp;';
3175         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
3176         $return .= '</div>';
3178         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3179     }
3183 class admin_setting_emoticons extends admin_setting {
3185     var $items;
3187     function admin_setting_emoticons() {
3188         global $CFG;
3189         $name = 'emoticons';
3190         $visiblename = get_string('emoticons', 'admin');
3191         $description = get_string('configemoticons', 'admin');
3192         $defaults = array('k0' => ':-)',
3193                           'v0' => 'smiley',
3194                           'k1' => ':)',
3195                           'v1' => 'smiley',
3196                           'k2' => ':-D',
3197                           'v2' => 'biggrin',
3198                           'k3' => ';-)',
3199                           'v3' => 'wink',
3200                           'k4' => ':-/',
3201                           'v4' => 'mixed',
3202                           'k5' => 'V-.',
3203                           'v5' => 'thoughtful',
3204                           'k6' => ':-P',
3205                           'v6' => 'tongueout',
3206                           'k7' => 'B-)',
3207                           'v7' => 'cool',
3208                           'k8' => '^-)',
3209                           'v8' => 'approve',
3210                           'k9' => '8-)',
3211                           'v9' => 'wideeyes',
3212                           'k10' => ':o)',
3213                           'v10' => 'clown',
3214                           'k11' => ':-(',
3215                           'v11' => 'sad',
3216                           'k12' => ':(',
3217                           'v12' => 'sad',
3218                           'k13' => '8-.',
3219                           'v13' => 'shy',
3220                           'k14' => ':-I',
3221                           'v14' => 'blush',
3222                           'k15' => ':-X',
3223                           'v15' => 'kiss',
3224                           'k16' => '8-o',
3225                           'v16' => 'surprise',
3226                           'k17' => 'P-|',
3227                           'v17' => 'blackeye',
3228                           'k18' => '8-[',
3229                           'v18' => 'angry',
3230                           'k19' => 'xx-P',
3231                           'v19' => 'dead',
3232                           'k20' => '|-.',
3233                           'v20' => 'sleepy',
3234                           'k21' => '}-]',
3235                           'v21' => 'evil',
3236                           'k22' => '(h)',
3237                           'v22' => 'heart',
3238                           'k23' => '(heart)',
3239                           'v23' => 'heart',
3240                           'k24' => '(y)',
3241                           'v24' => 'yes',
3242                           'k25' => '(n)',
3243                           'v25' => 'no',
3244                           'k26' => '(martin)',
3245                           'v26' => 'martin',
3246                           'k27' => '( )',
3247                           'v27' => 'egg');
3248         parent::admin_setting($name, $visiblename, $description, $defaults);
3249     }
3251     function get_setting() {
3252         global $CFG;
3253         $result = $this->config_read($this->name);
3254         if (is_null($result)) {
3255             return NULL;
3256         }
3257         $i = 0;
3258         $currentsetting = array();
3259         $items = explode('{;}', $result);
3260         foreach ($items as $item) {
3261           $item = explode('{:}', $item);
3262           $currentsetting['k'.$i] = $item[0];
3263           $currentsetting['v'.$i] = $item[1];
3264           $i++;
3265         }
3266         return $currentsetting;
3267     }
3269     function write_setting($data) {
3271         // there miiight be an easier way to do this :)
3272         // if this is changed, make sure the $defaults array above is modified so that this
3273         // function processes it correctly
3275         $keys = array();
3276         $values = array();
3278         foreach ($data as $key => $value) {
3279             if (substr($key,0,1) == 'k') {
3280                 $keys[substr($key,1)] = $value;
3281             } elseif (substr($key,0,1) == 'v') {
3282                 $values[substr($key,1)] = $value;
3283             }
3284         }
3286         $result = array();
3287         for ($i = 0; $i < count($keys); $i++) {
3288             if (($keys[$i] !== '') && ($values[$i] !== '')) {
3289                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).'{:}'.clean_param($values[$i], PARAM_NOTAGS);
3290             }
3291         }
3293         return ($this->config_write($this->name, implode('{;}', $result)) ? '' : get_string('errorsetting', 'admin').$this->visiblename.'<br />');
3294     }
3296     function output_html($data, $query='') {
3297         $fullname = $this->get_full_name();
3298         $return = '<div class="form-group">';
3299         for ($i = 0; $i < count($data) / 2; $i++) {
3300             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
3301             $return .= '&nbsp;&nbsp;';
3302             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
3303         }
3304         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
3305         $return .= '&nbsp;&nbsp;';
3306         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3307         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
3308         $return .= '&nbsp;&nbsp;';
3309         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
3310         $return .= '</div>';
3312         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3313     }
3317 /**
3318  * Setting for spellchecker language selection.
3319  */
3320 class admin_setting_special_editordictionary extends admin_setting_configselect {
3322     function admin_setting_special_editordictionary() {
3323         $name = 'editordictionary';
3324         $visiblename = get_string('editordictionary','admin');
3325         $description = get_string('configeditordictionary', 'admin');
3326         parent::admin_setting_configselect($name, $visiblename, $description, '', NULL);
3327     }
3329     function load_choices() {
3330         // function borrowed from the old moodle/admin/editor.php, slightly modified
3331         // Get all installed dictionaries in the system
3332         if (is_array($this->choices)) {
3333             return true;
3334         }
3336         $this->choices = array();
3338         global $CFG;
3340         clearstatcache();
3342         // If aspellpath isn't set don't even bother ;-)
3343         if (empty($CFG->aspellpath)) {
3344             $this->choices['error'] = 'Empty aspell path!';
3345             return true;
3346         }
3348         // Do we have access to popen function?
3349         if (!function_exists('popen')) {
3350             $this->choices['error'] = 'Popen function disabled!';
3351             return true;
3352         }
3354         $cmd          = $CFG->aspellpath;
3355         $output       = '';
3356         $dictionaries = array();
3358         if(!($handle = @popen(escapeshellarg($cmd).' dump dicts', 'r'))) {
3359             $this->choices['error'] = 'Couldn\'t create handle!';
3360         }
3362         while(!feof($handle)) {
3363             $output .= fread($handle, 1024);
3364         }
3365         @pclose($handle);
3367         $dictionaries = explode(chr(10), $output);
3368         foreach ($dictionaries as $dict) {
3369             if (empty($dict)) {
3370                 continue;
3371             }
3372             $this->choices[$dict] = $dict;
3373         }
3375         if (empty($this->choices)) {
3376             $this->choices['error'] = 'Error! Check your aspell installation!';
3377         }
3378         return true;
3379     }
3383 class admin_setting_special_editorhidebuttons extends admin_setting {
3384     var $items;
3386     function admin_setting_special_editorhidebuttons() {
3387         parent::admin_setting('editorhidebuttons', get_string('editorhidebuttons', 'admin'),
3388                               get_string('confeditorhidebuttons', 'admin'), array());
3389         // weird array... buttonname => buttonimage (assume proper path appended). if you leave buttomimage blank, text will be printed instead
3390         $this->items = array('fontname' => '',
3391                          'fontsize' => '',
3392                          'formatblock' => '',
3393                          'bold' => 'ed_format_bold.gif',
3394                          'italic' => 'ed_format_italic.gif',
3395                          'underline' => 'ed_format_underline.gif',
3396                          'strikethrough' => 'ed_format_strike.gif',
3397                          'subscript' => 'ed_format_sub.gif',
3398                          'superscript' => 'ed_format_sup.gif',
3399                          'copy' => 'ed_copy.gif',
3400                          'cut' => 'ed_cut.gif',
3401                          'paste' => 'ed_paste.gif',
3402                          'clean' => 'ed_wordclean.gif',
3403                          'undo' => 'ed_undo.gif',
3404                          'redo' => 'ed_redo.gif',
3405                          'justifyleft' => 'ed_align_left.gif',
3406                          'justifycenter' => 'ed_align_center.gif',
3407                          'justifyright' => 'ed_align_right.gif',
3408                          'justifyfull' => 'ed_align_justify.gif',
3409                          'lefttoright' => 'ed_left_to_right.gif',
3410                          'righttoleft' => 'ed_right_to_left.gif',
3411                          'insertorderedlist' => 'ed_list_num.gif',
3412                          'insertunorderedlist' => 'ed_list_bullet.gif',
3413                          'outdent' => 'ed_indent_less.gif',
3414                          'indent' => 'ed_indent_more.gif',
3415                          'forecolor' => 'ed_color_fg.gif',
3416                          'hilitecolor' => 'ed_color_bg.gif',
3417                          'inserthorizontalrule' => 'ed_hr.gif',
3418                          'createanchor' => 'ed_anchor.gif',
3419                          'createlink' => 'ed_link.gif',
3420                          'unlink' => 'ed_unlink.gif',
3421                          'insertimage' => 'ed_image.gif',
3422                          'inserttable' => 'insert_table.gif',
3423                          'insertsmile' => 'em.icon.smile.gif',
3424                          'insertchar' => 'icon_ins_char.gif',
3425                          'spellcheck' => 'spell-check.gif',
3426                          'htmlmode' => 'ed_html.gif',
3427                          'popupeditor' => 'fullscreen_maximize.gif',
3428                          'search_replace' => 'ed_replace.gif');
3429     }
3431     function get_setting() {
3432         $result = $this->config_read($this->name);
3433         if (is_null($result)) {
3434             return NULL;
3435         }
3436         if ($result === '') {
3437             return array();
3438         }
3439         return explode(' ', $result);
3440     }
3442     function write_setting($data) {
3443         if (!is_array($data)) {
3444             return ''; // ignore it
3445         }
3446         unset($data['xxxxx']);
3447         $result = array();
3449         foreach ($data as $key => $value) {
3450             if (!isset($this->items[$key])) {
3451                 return get_string('errorsetting', 'admin');
3452             }
3453             if ($value == '1') {
3454                 $result[] = $key;
3455             }
3456         }
3457         return ($this->config_write($this->name, implode(' ', $result)) ? '' : get_string('errorsetting', 'admin'));
3458     }
3460     function output_html($data, $query='') {
3462         global $CFG;
3464         // checkboxes with input name="$this->name[$key]" value="1"
3465         // we do 15 fields per column
3467         $return = '<div class="form-group">';
3468         $return .= '<table><tr><td valign="top" align="right">';
3469         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3471         $count = 0;
3473         foreach($this->items as $key => $value) {
3474             if ($count % 15 == 0 and $count != 0) {
3475                 $return .= '</td><td valign="top" align="right">';
3476             }
3478             $return .= '<label for="'.$this->get_id().$key.'">';
3479             $return .= ($value == '' ? get_string($key,'editor') : '<img width="18" height="18" src="'.$CFG->wwwroot.'/lib/editor/htmlarea/images/'.$value.'" alt="'.get_string($key,'editor').'" title="'.get_string($key,'editor').'" />').'&nbsp;';
3480             $return .= '<input type="checkbox" class="form-checkbox" value="1" id="'.$this->get_id().$key.'" name="'.$this->get_full_name().'['.$key.']"'.(in_array($key,$data) ? ' checked="checked"' : '').' />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
3481             $return .= '</label>';
3482             $count++;
3483             if ($count % 15 != 0) {
3484                 $return .= '<br /><br />';
3485             }
3486         }
3488         $return .= '</td></tr>';
3489         $return .= '</table>';
3490         $return .= '</div>';
3492         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3493     }
3496 /**
3497  * Special setting for limiting of the list of available languages.
3498  */
3499 class admin_setting_langlist extends admin_setting_configtext {
3500     function admin_setting_langlist() {
3501         parent::admin_setting_configtext('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
3502     }
3504     function write_setting($data) {
3505         $return = parent::write_setting($data);
3506         get_list_of_languages(true);//refresh the list
3507         return $return;
3508     }
3511 /**
3512  * Course category selection
3513  */
3514 class admin_settings_coursecat_select extends admin_setting_configselect {
3515     function admin_settings_coursecat_select($name, $visiblename, $description, $defaultsetting) {
3516         parent::admin_setting_configselect($name, $visiblename, $description, $defaultsetting, NULL);
3517     }
3519     function load_choices() {
3520         global $CFG;
3521         require_once($CFG->dirroot.'/course/lib.php');
3522         if (is_array($this->choices)) {
3523             return true;
3524         }
3525         $this->choices = make_categories_options();
3526         return true;
3527     }
3530 class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
3531     function admin_setting_special_backupdays() {
3532         parent::admin_setting_configmulticheckbox2('backup_sche_weekdays', get_string('schedule'), get_string('backupschedulehelp'), array(), NULL);
3533         $this->plugin = 'backup';
3534     }
3536     function load_choices() {
3537         if (is_array($this->choices)) {
3538             return true;
3539         }
3540         $this->choices = array();
3541         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
3542         foreach ($days as $day) {
3543             $this->choices[$day] = get_string($day, 'calendar');
3544         }
3545         return true;
3546     }
3549 /**
3550  * Special debug setting
3551  */
3552 class admin_setting_special_debug extends admin_setting_configselect {
3553     function admin_setting_special_debug() {
3554         parent::admin_setting_configselect('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
3555     }
3557     function load_choices() {
3558         if (is_array($this->choices)) {
3559             return true;
3560         }
3561         $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
3562                                DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
3563                                DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
3564                                DEBUG_ALL       => get_string('debugall', 'admin'),
3565                                DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
3566         return true;
3567     }
3571 class admin_setting_special_calendar_weekend extends admin_setting {
3572     function admin_setting_special_calendar_weekend() {
3573         $name = 'calendar_weekend';
3574         $visiblename = get_string('calendar_weekend', 'admin');
3575         $description = get_string('helpweekenddays', 'admin');
3576         $default = array ('0', '6'); // Saturdays and Sundays
3577         parent::admin_setting($name, $visiblename, $description, $default);
3578     }
3580     function get_setting() {
3581         $result = $this->config_read($this->name);
3582         if (is_null($result)) {
3583             return NULL;
3584         }
3585         if ($result === '') {
3586             return array();
3587         }
3588         $settings = array();
3589         for ($i=0; $i<7; $i++) {
3590             if ($result & (1 << $i)) {
3591                 $setting[] = $i;
3592             }
3593         }
3594         return $setting;
3595     }
3597     function write_setting($data) {
3598         if (!is_array($data)) {
3599             return '';
3600         }
3601         unset($data['xxxxx']);
3602         $result = 0;
3603         foreach($data as $index) {
3604             $result |= 1 << $index;
3605         }
3606         return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
3607     }
3609     function output_html($data, $query='') {
3610         // The order matters very much because of the implied numeric keys
3611         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
3612         $return = '<table><thead><tr>';
3613         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3614         foreach($days as $index => $day) {
3615             $return .= '<td><label for="'.$this->get_id().$index.'">'.get_string($day, 'calendar').'</label></td>';
3616         }
3617         $return .= '</tr></thead><tbody><tr>';
3618         foreach($days as $index => $day) {
3619             $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>';
3620         }
3621         $return .= '</tr></tbody></table>';
3623         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3625     }
3629 /**
3630  * Admin setting that allows a user to pick appropriate roles for something.
3631  */
3632 class admin_setting_pickroles extends admin_setting_configmulticheckbox {
3633     private $types;
3635     /**
3636      * @param string $name Name of config variable
3637      * @param string $visiblename Display name
3638      * @param string $description Description
3639      * @param array $types Array of capabilities (usually moodle/legacy:something)
3640      *   which identify roles that will be enabled by default. Default is the
3641      *   student role
3642      */
3643     function admin_setting_pickroles($name, $visiblename, $description, $types) {
3644         parent::admin_setting_configmulticheckbox($name, $visiblename, $description, NULL, NULL);
3645         $this->types = $types;
3646     }
3648     function load_choices() {
3649         global $CFG, $DB;
3650         if (empty($CFG->rolesactive)) {
3651             return false;
3652         }
3653         if (is_array($this->choices)) {
3654             return true;
3655         }
3656         if ($roles = $DB->get_records('role')) {
3657             $this->choices = array();
3658             foreach($roles as $role) {
3659                 $this->choices[$role->id] = format_string($role->name);
3660             }
3661             return true;
3662         } else {
3663             return false;
3664         }
3665     }
3667     function get_defaultsetting() {
3668         global $CFG;
3670         if (empty($CFG->rolesactive)) {
3671             return null;
3672         }
3673         $result = array();
3674         foreach($this->types as $capability) {
3675             if ($caproles = get_roles_with_capability($capability, CAP_ALLOW)) {
3676                 foreach ($caproles as $caprole) {
3677                     $result[$caprole->id] = 1;
3678                 }
3679             }
3680         }
3681         return $result;
3682     }
3685 /**
3686  * Text field linked to config_plugins for the quiz, with an advanced checkbox.
3687  */
3688 class admin_setting_quiz_text extends admin_setting_configtext {
3689     function __construct($name, $visiblename, $description, $defaultsetting, $paramtype) {
3690         $this->plugin = 'quiz';
3691         parent::admin_setting_configtext($name, $visiblename, $description,
3692                 $defaultsetting, $paramtype);
3693     }
3695     function get_setting() {
3696         $value = parent::get_setting();
3697         $fix = $this->config_read('fix_' . $this->name);
3698         if (is_null($value) or is_null($fix)) {
3699             return NULL;
3700         }
3701         return array('value' => $value, 'fix' => $fix);
3702     }
3704     function write_setting($data) {
3705         $error = parent::write_setting($data['value']);
3706         if (!$error) {
3707             if (empty($data['fix'])) {
3708                 $ok = $this->config_write('fix_' . $this->name, 0);
3709             } else {
3710                 $ok = $this->config_write('fix_' . $this->name, 1);
3711             }
3712             if (!$ok) {
3713                 $error = get_string('errorsetting', 'admin');
3714             }
3715         }
3716         return $error;
3717     }
3719     function output_html($data, $query='') {
3720         $default = $this->get_defaultsetting();
3721         $defaultinfo = array();
3722         if (isset($this->choices[$default['value']])) {
3723             $defaultinfo[] = $default['value'];
3724         }
3725         if (!empty($default['fix'])) {
3726             $defaultinfo[] = get_string('advanced');
3727         }
3728         $defaultinfo = implode(', ', $defaultinfo);
3730         $fix = !empty($data['fix']);
3731         $return = '<div class="form-text defaultsnext">' . 
3732                 '<input type="text" size="' . $this->size . '" id="' . $this->get_id() .
3733                 '" name="' . $this->get_full_name() . '[value]" value="' . s($data['value']) . '" />' .
3734                 ' <input type="checkbox" class="form-checkbox" id="' .
3735                 $this->get_id() . '_fix" name="' . $this->get_full_name() .
3736                 '[fix]" value="1" ' . ($fix ? 'checked="checked"' : '') . ' />' .
3737                 ' <label for="' . $this->get_id() . '_fix">' .
3738                 get_string('advanced') . '</label></div>';
3740         return format_admin_setting($this, $this->visiblename, $return,
3741                 $this->description, true, '', $defaultinfo, $query);
3742     }
3745 /**
3746  * Dropdown menu linked to config_plugins for the quiz, with an advanced checkbox.
3747  */
3748 class admin_setting_quiz_combo extends admin_setting_configselect {
3749     function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3750         $this->plugin = 'quiz';
3751         parent::admin_setting_configselect($name, $visiblename, $description,
3752                 $defaultsetting, $choices);
3753     }
3755     function get_setting() {
3756         $value = parent::get_setting();
3757         $fix = $this->config_read('fix_' . $this->name);
3758         if (is_null($value) or is_null($fix)) {
3759             return NULL;
3760         }
3761         return array('value' => $value, 'fix' => $fix);
3762     }
3764     function write_setting($data) {
3765         $error = parent::write_setting($data['value']);
3766         if (!$error) {
3767             if (empty($data['fix'])) {
3768                 $ok = $this->config_write('fix_' . $this->name, 0);
3769             } else {
3770                 $ok = $this->config_write('fix_' . $this->name, 1);
3771             }
3772             if (!$ok) {
3773                 $error = get_string('errorsetting', 'admin');
3774             }
3775         }
3776         return $error;
3777     }
3779     function output_html($data, $query='') {
3780         $default = $this->get_defaultsetting();
3781         $current = $this->get_setting();
3783         list($selecthtml, $warning) = $this->output_select_html($data['value'],
3784                 $current['value'], $default['value'], '[value]');
3785         if (!$selecthtml) {
3786             return '';
3787         }
3789         if (!is_null($default) and array_key_exists($default['value'], $this->choices)) {
3790             $defaultinfo = array();
3791             if (isset($this->choices[$default['value']])) {
3792                 $defaultinfo[] = $this->choices[$default['value']];
3793             }
3794             if (!empty($default['fix'])) {
3795                 $defaultinfo[] = get_string('advanced');
3796             }
3797             $defaultinfo = implode(', ', $defaultinfo);
3798         } else {
3799             $defaultinfo = '';
3800         }
3802         $fix = !empty($data['fix']);
3803         $return = '<div class="form-select defaultsnext">' . $selecthtml . 
3804                 ' <input type="checkbox" class="form-checkbox" id="' .
3805                 $this->get_id() . '_fix" name="' . $this->get_full_name() .
3806                 '[fix]" value="1" ' . ($fix ? 'checked="checked"' : '') . ' />' .
3807                 ' <label for="' . $this->get_id() . '_fix">' .
3808                 get_string('advanced') . '</label></div>';
3810         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
3811     }
3814 class admin_setting_quiz_reviewoptions extends admin_setting {
3815     private static $times = array(
3816             QUIZ_REVIEW_IMMEDIATELY => 'reviewimmediately',
3817             QUIZ_REVIEW_OPEN => 'reviewopen',
3818             QUIZ_REVIEW_CLOSED => 'reviewclosed');
3819     private static $things = array(
3820             QUIZ_REVIEW_RESPONSES => 'responses',
3821             QUIZ_REVIEW_ANSWERS => 'answers',
3822             QUIZ_REVIEW_FEEDBACK => 'feedback',
3823             QUIZ_REVIEW_GENERALFEEDBACK => 'generalfeedback',
3824             QUIZ_REVIEW_SCORES => 'scores',
3825             QUIZ_REVIEW_OVERALLFEEDBACK => 'overallfeedback');
3827     function __construct($name, $visiblename, $description, $defaultsetting) {
3828         $this->plugin = 'quiz';
3829         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
3830     }
3832     private function normalise_data($data) {
3833         $value = 0;
3834         foreach (admin_setting_quiz_reviewoptions::$times as $timemask => $timestring) {
3835             foreach (admin_setting_quiz_reviewoptions::$things as $thingmask => $thingstring) {
3836                 if (!empty($data[$timemask][$thingmask])) {
3837                     $value += $timemask & $thingmask;
3838                 }
3839             }
3840         }
3841         return $value;
3842     }
3844     function get_setting() {
3845         $value = $this->config_read($this->name);
3846         $fix = $this->config_read('fix_' . $this->name);
3847         if (is_null($value) or is_null($fix)) {
3848             return NULL;
3849         }
3850         return array('value' => $value, 'fix' => $fix);
3851     }
3853     function write_setting($data) {
3854         if (!isset($data['value'])) {
3855             $data['value'] = $this->normalise_data($data);
3856         }
3857         $ok = $this->config_write($this->name, $data['value']);
3858         if ($ok) {
3859             if (empty($data['fix'])) {
3860                 $ok = $this->config_write('fix_' . $this->name, 0);
3861             } else {
3862                 $ok = $this->config_write('fix_' . $this->name, 1);
3863             }
3864         }
3865         if (!$ok) {
3866             return get_string('errorsetting', 'admin');
3867         }
3868         return '';
3869     }
3871     function output_html($data, $query='') {
3872         if (!isset($data['value'])) {
3873             $data['value'] = $this->normalise_data($data);
3874         }
3876         $return = '<div id="adminquizreviewoptions" class="clearfix">' . "\n";
3877         foreach (admin_setting_quiz_reviewoptions::$times as $timemask => $timestring) {
3878             $return .= '<div class="group"><div class="fitemtitle">' . get_string($timestring, 'quiz') . "</div>\n";
3879             $nameprefix = $this->get_full_name() . '[' . $timemask . ']';
3880             $idprefix = $this->get_id(). '_' . $timemask . '_';
3881             foreach (admin_setting_quiz_reviewoptions::$things as $thingmask => $thingstring) {
3882                 $id = $idprefix . $thingmask;
3883                 $state = '';
3884                 if ($data['value'] & $timemask & $thingmask) {
3885                     $state = 'checked="checked" ';
3886                 }
3887                 $return .= '<span><input type="checkbox" name="' .
3888                         $nameprefix . '[' . $thingmask . ']" value="1" id="' . $id .
3889                         '" ' . $state . '/> <label for="' . $id . '">' . 
3890                         get_string($thingstring, 'quiz') . "</label></span>\n";
3891             }
3892             $return .= "</div>\n";
3893         }
3894         $return .= "</div>\n";
3896         $fix = !empty($data['fix']);
3897         $return .= '<input type="checkbox" class="form-checkbox" id="' .
3898                 $this->get_id() . '_fix" name="' . $this->get_full_name() .
3899                 '[fix]" value="1" ' . ($fix ? 'checked="checked"' : '') . ' />' .
3900                 ' <label for="' . $this->get_id() . '_fix">' .
3901                 get_string('advanced') . '</label> ';
3903         return format_admin_setting($this, $this->visiblename, $return,
3904                 $this->description, true, '', get_string('everythingon', 'quiz'), $query);
3905     }
3908 /**
3909  * Specialisation of admin_setting_quiz_combo for easy yes/no choices.
3910  */
3911 class admin_setting_quiz_yesno extends admin_setting_quiz_combo {
3912     function __construct($name, $visiblename, $description, $defaultsetting) {
3913         $this->plugin = 'quiz';
3914         parent::__construct($name, $visiblename, $description,
3915                 $defaultsetting, array(get_string('no'), get_string('yes')));
3916     }
3919 /**
3920  * Graded roles in gradebook
3921  */
3922 class admin_setting_special_gradebookroles extends admin_setting_pickroles {
3923     function admin_setting_special_gradebookroles() {
3924         parent::admin_setting_pickroles('gradebookroles', get_string('gradebookroles', 'admin'),
3925                                                   get_string('configgradebookroles', 'admin'),
3926                                                   array('moodle/legacy:student'));
3927     }
3931 class admin_setting_regradingcheckbox extends admin_setting_configcheckbox {
3932     function write_setting($data) {
3933         global $CFG, $DB;
3935         $oldvalue  = $this->config_read($this->name);
3936         $return    = parent::write_setting($data);
3937         $newvalue  = $this->config_read($this->name);
3939         if ($oldvalue !== $newvalue) {
3940             // force full regrading
3941             $DB->set_field('grade_items', 'needsupdate', 1, array('needsupdate'=>0));
3942         }
3944         return $return;
3945     }
3948 /**
3949  * Which roles to show on course decription page
3950  */
3951 class admin_setting_special_coursemanager extends admin_setting_pickroles {
3952     function admin_setting_special_coursemanager() {
3953         parent::admin_setting_pickroles('coursemanager', get_string('coursemanager', 'admin'),
3954                                                   get_string('configcoursemanager', 'admin'),
3955                                                   array('moodle/legacy:editingteacher'));
3956     }
3959 /**
3960  * Primary grade export plugin - has state tracking.
3961  */
3962 class admin_setting_special_gradeexport extends admin_setting_configmulticheckbox {
3963     function admin_setting_special_gradeexport() {
3964         parent::admin_setting_configmulticheckbox('gradeexport', get_string('gradeexport', 'admin'),
3965                                                   get_string('configgradeexport', 'admin'), array(), NULL);
3966     }
3968     function load_choices() {
3969         if (is_array($this->choices)) {
3970             return true;
3971         }
3972         $this->choices = array();
3974         if ($plugins = get_list_of_plugins('grade/export')) {
3975             foreach($plugins as $plugin) {
3976                 $this->choices[$plugin] = get_string('modulename', 'gradeexport_'.$plugin);
3977             }
3978         }
3979         return true;
3980     }
3983 /**
3984  * Grade category settings
3985  */
3986 class admin_setting_gradecat_combo extends admin_setting {
3988     var $choices;
3990     function admin_setting_gradecat_combo($name, $visiblename, $description, $defaultsetting, $choices) {
3991         $this->choices = $choices;
3992         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
3993     }
3995     function get_setting() {
3996         global $CFG;
3998         $value = $this->config_read($this->name);
3999         $flag  = $this->config_read($this->name.'_flag');
4001         if (is_null($value) or is_null($flag)) {
4002             return NULL;
4003         }
4005         $flag   = (int)$flag;
4006         $forced = (boolean)(1 & $flag); // first bit
4007         $adv    = (boolean)(2 & $flag); // second bit
4009         return array('value' => $value, 'forced' => $forced, 'adv' => $adv);
4010     }
4012     function write_setting($data) {
4013         global $CFG;
4015         $value  = $data['value'];
4016         $forced = empty($data['forced']) ? 0 : 1;
4017         $adv    = empty($data['adv'])    ? 0 : 2;
4018         $flag   = ($forced | $adv); //bitwise or
4020         if (!in_array($value, array_keys($this->choices))) {
4021             return 'Error setting ';
4022         }
4024         $oldvalue  = $this->config_read($this->name);
4025         $oldflag   = (int)$this->config_read($this->name.'_flag');
4026         $oldforced = (1 & $oldflag); // first bit
4028         $result1 = $this->config_write($this->name, $value);
4029         $result2 = $this->config_write($this->name.'_flag', $flag);
4031         // force regrade if needed
4032         if ($oldforced != $forced or ($forced and $value != $oldvalue)) {
4033            require_once($CFG->libdir.'/gradelib.php');
4034            grade_category::updated_forced_settings();
4035         }
4037         if ($result1 and $result2) {
4038             return '';
4039         } else {
4040             return get_string('errorsetting', 'admin');
4041         }
4042     }
4044     function output_html($data, $query='') {
4045         $value  = $data['value'];
4046         $forced = !empty($data['forced']);
4047         $adv    = !empty($data['adv']);
4049         $default = $this->get_defaultsetting();
4050         if (!is_null($default)) {
4051             $defaultinfo = array();
4052             if (isset($this->choices[$default['value']])) {
4053                 $defaultinfo[] = $this->choices[$default['value']];
4054             }
4055             if (!empty($default['forced'])) {
4056                 $defaultinfo[] = get_string('force');
4057             }
4058             if (!empty($default['adv'])) {
4059                 $defaultinfo[] = get_string('advanced');
4060             }
4061             $defaultinfo = implode(', ', $defaultinfo);
4063         } else {
4064             $defaultinfo = NULL;
4065         }
4068         $return = '<div class="form-group">';
4069         $return .= '<select class="form-select" id="'.$this->get_id().'" name="'.$this->get_full_name().'[value]">';
4070         foreach ($this->choices as $key => $val) {
4071             // the string cast is needed because key may be integer - 0 is equal to most strings!
4072             $return .= '<option value="'.$key.'"'.((string)$key==$value ? ' selected="selected"' : '').'>'.$val.'</option>';
4073         }
4074         $return .= '</select>';
4075         $return .= '<input type="checkbox" class="form-checkbox" id="'.$this->get_id().'force" name="'.$this->get_full_name().'[forced]" value="1" '.($forced ? 'checked="checked"' : '').' />'
4076                   .'<label for="'.$this->get_id().'force">'.get_string('force').'</label>';
4077         $return .= '<input type="checkbox" class="form-checkbox" id="'.$this->get_id().'adv" name="'.$this->get_full_name().'[adv]" value="1" '.($adv ? 'checked="checked"' : '').' />'
4078                   .'<label for="'.$this->get_id().'adv">'.get_string('advanced').'</label>';
4079         $return .= '</div>';
4081         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
4082     }
4086 /**
4087  * Selection of grade report in user profiles
4088  */
4089 class admin_setting_grade_profilereport extends admin_setting_configselect {
4090     function admin_setting_grade_profilereport() {
4091         parent::admin_setting_configselect('grade_profilereport', get_string('profilereport', 'grades'), get_string('configprofilereport', 'grades'), 'user', null);
4092     }
4094     function load_choices() {
4095         if (is_array($this->choices)) {
4096             return true;
4097         }
4098         $this->choices = array();
4100         global $CFG;
4101         require_once($CFG->libdir.'/gradelib.php');
4103         foreach (get_list_of_plugins('grade/report') as $plugin) {
4104             if (file_exists($CFG->dirroot.'/grade/report/'.$plugin.'/lib.php')) {
4105                 require_once($CFG->dirroot.'/grade/report/'.$plugin.'/lib.php');
4106                 $functionname = 'grade_report_'.$plugin.'_profilereport';
4107                 if (function_exists($functionname)) {
4108                     $this->choices[$plugin] = get_string('modulename', 'gradereport_'.$plugin, NULL, $CFG->dirroot.'/grade/report/'.$plugin.'/lang/');
4109                 }
4110             }
4111         }
4112         return true;
4113     }
4116 /**
4117  * Special class for register auth selection
4118  */
4119 class admin_setting_special_registerauth extends admin_setting_configselect {
4120     function admin_setting_special_registerauth() {
4121         parent::admin_setting_configselect('registerauth', get_string('selfregistration', 'auth'), get_string('selfregistration_help', 'auth'), '', null);
4122     }
4124     function get_defaultsettings() {
4125         $this->load_choices();
4126         if (array_key_exists($this->defaultsetting, $this->choices)) {
4127             return $this->defaultsetting;
4128         } else {
4129             return '';
4130         }
4131     }
4133     function load_choices() {
4134         global $CFG;
4136         if (is_array($this->choices)) {
4137             return true;
4138         }
4139         $this->choices = array();
4140         $this->choices[''] = get_string('disable');
4142         $authsenabled = get_enabled_auth_plugins(true);
4144         foreach ($authsenabled as $auth) {
4145             $authplugin = get_auth_plugin($auth);
4146             if (!$authplugin->can_signup()) {
4147                 continue;
4148             }
4149             // Get the auth title (from core or own auth lang files)
4150             $authtitle = $authplugin->get_title();
4151             $this->choices[$auth] = $authtitle;
4152         }
4153         return true;
4154     }
4157 /**
4158  * Module manage page
4159  */
4160 class admin_page_managemods extends admin_externalpage {
4161     function admin_page_managemods() {
4162         global $CFG;
4163         parent::admin_externalpage('managemodules', get_string('modsettings', 'admin'), "$CFG->wwwroot/$CFG->admin/modules.php");
4164     }
4166     function search($query) {
4167         global $DB;
4168         if ($result = parent::search($query)) {
4169             return $result;
4170         }
4172         $found = false;
4173         if ($modules = $DB->get_records('modules')) {
4174             $textlib = textlib_get_instance();
4175             foreach ($modules as $module) {
4176                 if (strpos($module->name, $query) !== false) {
4177                     $found = true;
4178                     break;
4179                 }
4180                 $strmodulename = get_string('modulename', $module->name);
4181                 if (strpos($textlib->strtolower($strmodulename), $query) !== false) {
4182                     $found = true;
4183                     break;
4184                 }
4185             }
4186         }
4187         if ($found) {
4188             $result = new object();
4189             $result->page     = $this;
4190             $result->settings = array();
4191             return array($this->name => $result);
4192         } else {
4193             return array();
4194         }
4195     }
4198 /**
4199  * Enrolment manage page
4200  */
4201 class admin_enrolment_page extends admin_externalpage {
4202     public function admin_enrolment_page() {
4203         global $CFG;
4204         parent::admin_externalpage('enrolment', get_string('enrolments'), $CFG->wwwroot . '/'.$CFG->admin.'/enrol.php');
4205     }
4207     function search($query) {
4208         if ($result = parent::search($query)) {
4209             return $result;
4210         }
4212         $found = false;
4214         if ($modules = get_list_of_plugins('enrol')) {
4215             $textlib = textlib_get_instance();
4216             foreach ($modules as $plugin) {
4217                 if (strpos($plugin, $query) !== false) {
4218                     $found = true;
4219                     break;
4220                 }