MDL-16180 - add portfolio support to mnet
[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     if ($plugins = get_list_of_plugins('portfolio/type', 'db')) {
286         foreach ($plugins as $plugin) {
287             $dbdirs[] = $CFG->dirroot . '/portfolio/type/' . $plugin . '/db';
288         }
289     }
291 /// Local database changes, if the local folder exists.
292     if (file_exists($CFG->dirroot . '/local')) {
293         $dbdirs[] = $CFG->dirroot.'/local/db';
294     }
296     return $dbdirs;
299 /**
300  * Upgrade plugins
301  *
302  * @uses $CFG
303  * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
304  * @param string $dir  The directory where the plugins are located (e.g. 'question/questiontypes')
305  * @param string $return The url to prompt the user to continue to
306  */
307 function upgrade_plugins($type, $dir, $return) {
308     global $CFG, $interactive, $DB;
310 /// Let's know if the header has been printed, so the funcion is being called embedded in an outer page
311     $embedded = defined('HEADER_PRINTED');
313     $plugs = get_list_of_plugins($dir);
314     $updated_plugins = false;
315     $strpluginsetup  = get_string('pluginsetup');
317     foreach ($plugs as $plug) {
319         $fullplug = $CFG->dirroot .'/'.$dir.'/'. $plug;
321         unset($plugin);
323         if (is_readable($fullplug .'/version.php')) {
324             include_once($fullplug .'/version.php');  // defines $plugin with version etc
325         } else {
326             continue;                              // Nothing to do.
327         }
329         $newupgrade = false;
330         if (is_readable($fullplug . '/db/upgrade.php')) {
331             include_once($fullplug . '/db/upgrade.php');  // defines new upgrading function
332             $newupgrade = true;
333         }
335         if (!isset($plugin)) {
336             continue;
337         }
339         if (!empty($plugin->requires)) {
340             if ($plugin->requires > $CFG->version) {
341                 $info = new object();
342                 $info->pluginname = $plug;
343                 $info->pluginversion  = $plugin->version;
344                 $info->currentmoodle = $CFG->version;
345                 $info->requiremoodle = $plugin->requires;
346                 if (!$updated_plugins && !$embedded) {
347                     print_header($strpluginsetup, $strpluginsetup,
348                         build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
349                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
350                 }
351                 upgrade_log_start();
352                 notify(get_string('pluginrequirementsnotmet', 'error', $info));
353                 $updated_plugins = true;
354                 continue;
355             }
356         }
358         $plugin->name = $plug;   // The name MUST match the directory
360         $pluginversion = $type.'_'.$plug.'_version';
362         if (!isset($CFG->$pluginversion)) {
363             set_config($pluginversion, 0);
364         }
366         if ($CFG->$pluginversion == $plugin->version) {
367             // do nothing
368         } else if ($CFG->$pluginversion < $plugin->version) {
369             if (!$updated_plugins && !$embedded) {
370                 print_header($strpluginsetup, $strpluginsetup,
371                         build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
372                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
373             }
374             $updated_plugins = true;
375             upgrade_log_start();
376             print_heading($dir.'/'. $plugin->name .' plugin needs upgrading');
377             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
378                 $DB->set_debug(true);
379             }
380             @set_time_limit(0);  // To allow slow databases to complete the long SQL
382             if ($CFG->$pluginversion == 0) {    // It's a new install of this plugin
383             /// Both old .sql files and new install.xml are supported
384             /// but we priorize install.xml (XMLDB) if present
385                 if (file_exists($fullplug . '/db/install.xml')) {
386                     $DB->get_manager()->install_from_xmldb_file($fullplug . '/db/install.xml'); //New method
387                 }
388                 $status = true;
389                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
390                     $DB->set_debug(false);
391                 }
392             /// Continue with the instalation, roles and other stuff
393                 if ($status) {
394                 /// OK so far, now update the plugins record
395                     set_config($pluginversion, $plugin->version);
397                 /// Install capabilities
398                     if (!update_capabilities($type.'/'.$plug)) {
399                         print_error('cannotsetupcapforplugin', '', '', $plugin->name);
400                     }
401                 /// Install events
402                     events_update_definition($type.'/'.$plug);
404                 /// Install message providers
405                     message_update_providers($type.'/'.$plug);
407                 /// Run local install function if there is one
408                     if (is_readable($fullplug .'/lib.php')) {
409                         include_once($fullplug .'/lib.php');
410                         $installfunction = $plugin->name.'_install';
411                         if (function_exists($installfunction)) {
412                             if (! $installfunction() ) {
413                                 notify('Encountered a problem running install function for '.$module->name.'!');
414                             }
415                         }
416                     }
418                     notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
419                 } else {
420                     notify('Installing '. $plugin->name .' FAILED!');
421                 }
422             } else {                            // Upgrade existing install
423             /// Run de old and new upgrade functions for the module
424                 $newupgrade_function = 'xmldb_' . $type.'_'.$plugin->name .'_upgrade';
426             /// Then, the new function if exists and the old one was ok
427                 $newupgrade_status = true;
428                 if ($newupgrade && function_exists($newupgrade_function)) {
429                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
430                         $DB->set_debug(true);
431                     }
432                     $newupgrade_status = $newupgrade_function($CFG->$pluginversion);
433                 } else if ($newupgrade) {
434                     notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
435                              $fullplug . '/db/upgrade.php');
436                 }
437                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
438                     $DB->set_debug(false);
439                 }
440             /// Now analyze upgrade results
441                 if ($newupgrade_status) {    // No upgrading failed
442                 /// OK so far, now update the plugins record
443                     set_config($pluginversion, $plugin->version);
444                     if (!update_capabilities($type.'/'.$plug)) {
445                         print_error('cannotupdateplugincap', '', '', $plugin->name);
446                     }
447                 /// Update events
448                     events_update_definition($type.'/'.$plug);
450                 /// Update message providers
451                     message_update_providers($type.'/'.$plug);
453                     notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
454                 } else {
455                     notify('Upgrading '. $plugin->name .' from '. $CFG->$pluginversion .' to '. $plugin->version .' FAILED!');
456                 }
457             }
458             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
459                 echo '<hr />';
460             }
461         } else {
462             upgrade_log_start();
463             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$CFG->pluginversion, 'newversion'=>$plugin->version));
464         }
465     }
467     upgrade_log_finish();
469     if ($updated_plugins && !$embedded) {
470         if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
471             print_continue($return);
472             print_footer('none');
473             die;
474         } else if (CLI_UPGRADE && ($interactive > CLI_SEMI )) {
475             console_write(STDOUT,'askcontinue');
476             if (read_boolean()){
477                 return ;
478             } else {
479                 console_write(STDERR,'','',false);
480             }
481         }
482     }
485 /**
486  * Find and check all modules and load them up or upgrade them if necessary
487  *
488  * @uses $CFG
489  * @param string $return The url to prompt the user to continue to
490  * @todo Finish documenting this function
491  */
492 function upgrade_activity_modules($return) {
494     global $CFG, $interactive, $DB;
496     if (!$mods = get_list_of_plugins('mod') ) {
497         print_error('nomodules', 'debug');
498     }
500     $updated_modules = false;
501     $strmodulesetup  = get_string('modulesetup');
503     foreach ($mods as $mod) {
505         if ($mod == 'NEWMODULE') {   // Someone has unzipped the template, ignore it
506             continue;
507         }
509         $fullmod = $CFG->dirroot .'/mod/'. $mod;
511         unset($module);
513         if ( is_readable($fullmod .'/version.php')) {
514             include_once($fullmod .'/version.php');  // defines $module with version etc
515         } else {
516             notify('Module '. $mod .': '. $fullmod .'/version.php was not readable');
517             continue;
518         }
520         $newupgrade = false;
521         if ( is_readable($fullmod . '/db/upgrade.php')) {
522             include_once($fullmod . '/db/upgrade.php');  // defines new upgrading function
523             $newupgrade = true;
524         }
526         if (!isset($module)) {
527             continue;
528         }
530         if (!empty($module->requires)) {
531             if ($module->requires > $CFG->version) {
532                 $info = new object();
533                 $info->modulename = $mod;
534                 $info->moduleversion  = $module->version;
535                 $info->currentmoodle = $CFG->version;
536                 $info->requiremoodle = $module->requires;
537                 if (!$updated_modules) {
538                     print_header($strmodulesetup, $strmodulesetup,
539                             build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
540                             upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
541                 }
542                 upgrade_log_start();
543                 notify(get_string('modulerequirementsnotmet', 'error', $info));
544                 $updated_modules = true;
545                 continue;
546             }
547         }
549         $module->name = $mod;   // The name MUST match the directory
551         include_once($fullmod.'/lib.php');  // defines upgrading and/or installing functions
553         if ($currmodule = $DB->get_record('modules', array('name'=>$module->name))) {
554             if ($currmodule->version == $module->version) {
555                 // do nothing
556             } else if ($currmodule->version < $module->version) {
557             /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
558                 if (!$newupgrade) {
559                     notify('Upgrade file ' . $mod . ': ' . $fullmod . '/db/upgrade.php is not readable');
560                     continue;
561                 }
562                 if (!$updated_modules) {
563                     print_header($strmodulesetup, $strmodulesetup,
564                             build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
565                             upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
566                 }
567                 upgrade_log_start();
569                 print_heading($module->name .' module needs upgrading');
571             /// Run de old and new upgrade functions for the module
572                 $newupgrade_function = 'xmldb_' . $module->name . '_upgrade';
574             /// Then, the new function if exists and the old one was ok
575                 $newupgrade_status = true;
576                 if ($newupgrade && function_exists($newupgrade_function)) {
577                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
578                         $DB->set_debug(true);
579                     }
580                     $newupgrade_status = $newupgrade_function($currmodule->version, $module);
581                 } else if ($newupgrade) {
582                     notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
583                              $mod . ': ' . $fullmod . '/db/upgrade.php');
584                 }
585                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
586                     $DB->set_debug(false);
587                 }
588             /// Now analyze upgrade results
589                 if ($newupgrade_status) {    // No upgrading failed
590                     // OK so far, now update the modules record
591                     $module->id = $currmodule->id;
592                     if (!$DB->update_record('modules', $module)) {
593                         print_error('cannotupdatemod', '', '', $module->name);
594                     }
595                     remove_dir($CFG->dataroot . '/cache', true); // flush cache
596                     notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
597                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE) {
598                        echo '<hr />';
599                     }
600                 } else {
601                     notify('Upgrading '. $module->name .' from '. $currmodule->version .' to '. $module->version .' FAILED!');
602                 }
604             /// Update the capabilities table?
605                 if (!update_capabilities('mod/'.$module->name)) {
606                     print_error('cannotupdatemodcap', '', '', $module->name);
607                 }
609             /// Update events
610                 events_update_definition('mod/'.$module->name);
612             /// Update message providers
613                 message_update_providers('mod/'.$module->name);
615                 $updated_modules = true;
617             } else {
618                 upgrade_log_start();
619                 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$currmodule->version, 'newversion'=>$module->version));
620             }
622         } else {    // module not installed yet, so install it
623             if (!$updated_modules) {
624                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
625                     print_header($strmodulesetup, $strmodulesetup,
626                         build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
627                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
628                 }
629             }
630             upgrade_log_start();
631             print_heading($module->name);
632             $updated_modules = true;
633             // To avoid unnecessary output from the SQL queries in the CLI version
634             if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
635                 $DB->set_debug(true);
636             }
637             @set_time_limit(0);  // To allow slow databases to complete the long SQL
639         /// Both old .sql files and new install.xml are supported
640         /// but we priorize install.xml (XMLDB) if present
641             if (file_exists($fullmod . '/db/install.xml')) {
642                 $DB->get_manager()->install_from_xmldb_file($fullmod . '/db/install.xml'); //New method
643                 $status = true;
644             }
645             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
646                 $DB->set_debug(false);
647             }
649         /// Continue with the installation, roles and other stuff
650             if ($status) {
651                 if ($module->id = $DB->insert_record('modules', $module)) {
653                 /// Capabilities
654                     if (!update_capabilities('mod/'.$module->name)) {
655                         print_error('cannotsetupcapformod', '', '', $module->name);
656                     }
658                 /// Events
659                     events_update_definition('mod/'.$module->name);
661                 /// Message providers
662                     message_update_providers('mod/'.$module->name);
664                 /// Run local install function if there is one
665                     $installfunction = $module->name.'_install';
666                     if (function_exists($installfunction)) {
667                         if (! $installfunction() ) {
668                             notify('Encountered a problem running install function for '.$module->name.'!');
669                         }
670                     }
672                     notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
673                     if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
674                        echo '<hr />';
675                     }
676                 } else {
677                     print_error('cannotaddmodule', '', '', $module->name);
678                 }
679             } else {
680                 print_error('cannotsetuptable', 'debug', '', $module->name);
681             }
682         }
684     /// Check submodules of this module if necessary
686         $submoduleupgrade = $module->name.'_upgrade_submodules';
687         if (function_exists($submoduleupgrade)) {
688             $submoduleupgrade();
689         }
691     /// Run any defaults or final code that is necessary for this module
693         if ( is_readable($fullmod .'/defaults.php')) {
694             // Insert default values for any important configuration variables
695             unset($defaults);
696             include($fullmod .'/defaults.php'); // include here means execute, not library include
697             if (!empty($defaults)) {
698                 if (!empty($defaults['_use_config_plugins'])) {
699                     unset($defaults['_use_config_plugins']);
700                     $localcfg = get_config($module->name);
701                     foreach ($defaults as $name => $value) {
702                         if (!isset($localcfg->$name)) {
703                             set_config($name, $value, $module->name);
704                         }
705                     }
706                 } else {
707                     foreach ($defaults as $name => $value) {
708                         if (!isset($CFG->$name)) {
709                             set_config($name, $value);
710                         }
711                     }
712                 }
713             }
714         }
715     }
717     upgrade_log_finish(); // finish logging if started
719     if ($updated_modules) {
720         if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
721             print_continue($return);
722             print_footer('none');
723             die;
724         } else if ( CLI_UPGRADE && ($interactive > CLI_SEMI) ) {
725             console_write(STDOUT,'askcontinue');
726             if (read_boolean()){
727                 return ;
728             }else {
729                 console_write(STDERR,'','',false);
730             }
731         }
732     }
735 /**
736  * Try to obtain or release the cron lock.
737  *
738  * @param string  $name  name of lock
739  * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionaly
740  * @param bool $ignorecurrent ignore current lock state, usually entend previous lock
741  * @return bool true if lock obtained
742  */
743 function set_cron_lock($name, $until, $ignorecurrent=false) {
744     global $DB;
745     if (empty($name)) {
746         debugging("Tried to get a cron lock for a null fieldname");
747         return false;
748     }
750     // remove lock by force == remove from config table
751     if (is_null($until)) {
752         set_config($name, null);
753         return true;
754     }
756     if (!$ignorecurrent) {
757         // read value from db - other processes might have changed it
758         $value = $DB->get_field('config', 'value', array('name'=>$name));
760         if ($value and $value > time()) {
761             //lock active
762             return false;
763         }
764     }
766     set_config($name, $until);
767     return true;
770 function print_progress($done, $total, $updatetime=5, $sleeptime=1, $donetext='') {
771     static $thisbarid;
772     static $starttime;
773     static $lasttime;
775     if ($total < 2) {   // No need to show anything
776         return;
777     }
779     // Are we done?
780     if ($done >= $total) {
781         $done = $total;
782         if (!empty($thisbarid)) {
783             $donetext .= ' ('.$done.'/'.$total.') '.get_string('success');
784             print_progress_redraw($thisbarid, $done, $total, 500, $donetext);
785             $thisbarid = $starttime = $lasttime = NULL;
786         }
787         return;
788     }
790     if (empty($starttime)) {
791         $starttime = $lasttime = time();
792         $lasttime = $starttime - $updatetime;
793         $thisbarid = uniqid();
794         echo '<table width="500" cellpadding="0" cellspacing="0" align="center"><tr><td width="500">';
795         echo '<div id="bar'.$thisbarid.'" style="border-style:solid;border-width:1px;width:500px;height:50px;">';
796         echo '<div id="slider'.$thisbarid.'" style="border-style:solid;border-width:1px;height:48px;width:10px;background-color:green;"></div>';
797         echo '</div>';
798         echo '<div id="text'.$thisbarid.'" align="center" style="width:500px;"></div>';
799         echo '</td></tr></table>';
800         echo '</div>';
801     }
803     $now = time();
805     if ($done && (($now - $lasttime) >= $updatetime)) {
806         $elapsedtime = $now - $starttime;
807         $projectedtime = (int)(((float)$total / (float)$done) * $elapsedtime) - $elapsedtime;
808         $percentage = round((float)$done / (float)$total, 2);
809         $width = (int)(500 * $percentage);
811         if ($projectedtime > 10) {
812             $projectedtext = '  Ending: '.format_time($projectedtime);
813         } else {
814             $projectedtext = '';
815         }
817         $donetext .= ' ('.$done.'/'.$total.') '.$projectedtext;
818         print_progress_redraw($thisbarid, $done, $total, $width, $donetext);
820         $lasttime = $now;
821     }
824 // Don't call this function directly, it's called from print_progress.
825 function print_progress_redraw($thisbarid, $done, $total, $width, $donetext='') {
826     if (empty($thisbarid)) {
827         return;
828     }
829     echo '<script>';
830     echo 'document.getElementById("text'.$thisbarid.'").innerHTML = "'.addslashes_js($donetext).'";'."\n";
831     echo 'document.getElementById("slider'.$thisbarid.'").style.width = \''.$width.'px\';'."\n";
832     echo '</script>';
835 function upgrade_get_javascript() {
836     global $CFG, $SESSION;
838     if (!empty($SESSION->installautopilot)) {
839         $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = true;</script>'."\n";
840     } else {
841         $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = false;</script>'."\n";
842     }
843     $linktoscrolltoerrors .= '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>';
845     return $linktoscrolltoerrors;
848 function create_admin_user($user_input=NULL) {
849     global $CFG, $USER, $DB;
851     if (empty($CFG->rolesactive)) {   // No admin user yet.
853         $user = new object();
854         $user->auth         = 'manual';
855         $user->firstname    = get_string('admin');
856         $user->lastname     = get_string('user');
857         $user->username     = 'admin';
858         $user->password     = hash_internal_user_password('admin');
859         $user->email        = 'root@localhost';
860         $user->confirmed    = 1;
861         $user->mnethostid   = $CFG->mnet_localhost_id;
862         $user->lang         = $CFG->lang;
863         $user->maildisplay  = 1;
864         $user->timemodified = time();
866         if ($user_input) {
867             $user = $user_input;
868         }
869         if (!$user->id = $DB->insert_record('user', $user)) {
870             print_error('cannotcreateadminuser', 'debug');
871         }
873         if (!$user = $DB->get_record('user', array('id'=>$user->id))) {   // Double check.
874             print_error('invaliduserid');
875         }
877         // Assign the default admin roles to the new user.
878         if (!$adminroles = get_roles_with_capability('moodle/legacy:admin', CAP_ALLOW)) {
879             print_error('noadminrole', 'message');
880         }
881         $sitecontext = get_context_instance(CONTEXT_SYSTEM);
882         foreach ($adminroles as $adminrole) {
883             role_assign($adminrole->id, $user->id, 0, $sitecontext->id);
884         }
886         //set default message preferences
887         if (!message_set_default_message_preferences( $user )){
888             print_error('cannotsavemessageprefs', 'message');
889         }
891         set_config('rolesactive', 1);
893         // Log the user in.
894         $USER = get_complete_user_data('username', 'admin');
895         $USER->newadminuser = 1;
896         load_all_capabilities();
898         if (!defined('CLI_UPGRADE')||!CLI_UPGRADE) {
899           redirect("$CFG->wwwroot/user/editadvanced.php?id=$user->id");  // Edit thyself
900         }
901     } else {
902         print_error('cannotcreateadminuser', 'debug');
903     }
906 ////////////////////////////////////////////////
907 /// upgrade logging functions
908 ////////////////////////////////////////////////
910 /**
911  * Marks start of upgrade, blocks any other access to site.
912  * The upgrade is finished at the end of script or after timeout.
913  */
914 function start_upgrade() {
915     global $CFG;
917     static $started = false;
919     if ($started) {
920         upgrade_set_timeout(120);
922     } else {
923         ignore_user_abort(true);
924         register_shutdown_function('upgrade_finished_handler');
925         if ($CFG->version === '') {
926             // db not installed yet
927             $CFG->upgraderunning = time()+300;
928         } else {
929             set_config('upgraderunning', time()+300);
930         }
931         $started = true;
932     }
935 /**
936  * Internal function - executed at the very end of each upgrade.
937  */
938 function upgrade_finished_handler() {
939     upgrade_log_finish();
940     unset_config('upgraderunning');
941     ignore_user_abort(false);
944 /**
945  * Start logging of output into file (if not disabled) and
946  * prevent aborting and concurrent execution of upgrade script.
947  *
948  * Please note that you can not write into session variables after calling this function!
949  *
950  * This function may be called repeatedly.
951  */
952 function upgrade_log_start() {
953     global $upgradeloghandle;
955     start_upgrade(); // make sure the upgrade is started
957     if ($upgradeloghandle and ($upgradeloghandle !== 'error')) {
958         return;
959     }
961     make_upload_directory('upgradelogs');
962     ob_start('upgrade_log_callback', 2); // function for logging to disk; flush each line of text ASAP
965 /**
966  * Terminate logging of output, flush all data.
967  *
968  * Please make sure that each upgrade_log_start() is properly terminated by
969  * this function or print_error().
970  *
971  * This function may be called repeatedly.
972  */
973 function upgrade_log_finish() {
974     global $CFG, $upgradeloghandle, $upgradelogbuffer;
976     @ob_end_flush();
977     if ($upgradelogbuffer !== '') {
978         @fwrite($upgradeloghandle, $upgradelogbuffer);
979         $upgradelogbuffer = '';
980     }
981     if ($upgradeloghandle and ($upgradeloghandle !== 'error')) {
982         @fclose($upgradeloghandle);
983         $upgradeloghandle = false;
984     }
987 /**
988  * Callback function for logging into files. Not more than one file is created per minute,
989  * upgrade session (terminated by upgrade_log_finish()) is always stored in one file.
990  *
991  * This function must not output any characters or throw warnigns and errors!
992  */
993 function upgrade_log_callback($string) {
994     global $CFG, $upgradeloghandle, $upgradelogbuffer;
996     if (empty($CFG->disableupgradelogging) and ($string != '') and ($upgradeloghandle !== 'error')) {
997         if ($upgradeloghandle or ($upgradeloghandle = @fopen($CFG->dataroot.'/upgradelogs/upg_'.date('Ymd-Hi').'.html', 'a'))) {
998             $upgradelogbuffer .= $string;
999             if (strlen($upgradelogbuffer) > 2048) { // 2kB write buffer
1000                 @fwrite($upgradeloghandle, $upgradelogbuffer);
1001                 $upgradelogbuffer = '';
1002             }
1003         } else {
1004             $upgradeloghandle = 'error';
1005         }
1006     }
1007     return $string;
1010 /**
1011  * Test if and critical warnings are present
1012  * @return bool
1013  */
1014 function admin_critical_warnings_present() {
1015     global $SESSION;
1017     if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
1018         return 0;
1019     }
1021     if (!isset($SESSION->admin_critical_warning)) {
1022         $SESSION->admin_critical_warning = 0;
1023         if (ini_get_bool('register_globals')) {
1024             $SESSION->admin_critical_warning = 1;
1025         } else if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
1026             $SESSION->admin_critical_warning = 1;
1027         }
1028     }
1030     return $SESSION->admin_critical_warning;
1033 /**
1034  * Try to verify that dataroot is not accessible from web.
1035  * It is not 100% correct but might help to reduce number of vulnerable sites.
1036  *
1037  * Protection from httpd.conf and .htaccess is not detected properly.
1038  * @param bool $fetchtest try to test public access by fetching file
1039  * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING migth be problematic
1040  */
1041 function is_dataroot_insecure($fetchtest=false) {
1042     global $CFG;
1044     $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
1046     $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
1047     $rp = strrev(trim($rp, '/'));
1048     $rp = explode('/', $rp);
1049     foreach($rp as $r) {
1050         if (strpos($siteroot, '/'.$r.'/') === 0) {
1051             $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
1052         } else {
1053             break; // probably alias root
1054         }
1055     }
1057     $siteroot = strrev($siteroot);
1058     $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
1060     if (strpos($dataroot, $siteroot) !== 0) {
1061         return false;
1062     }
1064     if (!$fetchtest) {
1065         return INSECURE_DATAROOT_WARNING;
1066     }
1068     // now try all methods to fetch a test file using http protocol
1070     $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
1071     preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
1072     $httpdocroot = $matches[1];
1073     $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
1074     if (make_upload_directory('diag', false) === false) {
1075         return INSECURE_DATAROOT_WARNING;
1076     }
1077     $testfile = $CFG->dataroot.'/diag/public.txt';
1078     if (!file_exists($testfile)) {
1079         file_put_contents($testfile, 'test file, do not delete');
1080     }
1081     $teststr = trim(file_get_contents($testfile));
1082     if (empty($teststr)) {
1083         // hmm, strange
1084         return INSECURE_DATAROOT_WARNING;
1085     }
1087     $testurl = $datarooturl.'/diag/public.txt';
1089     if (extension_loaded('curl') and ($ch = @curl_init($testurl)) !== false) {
1090         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1091         curl_setopt($ch, CURLOPT_HEADER, false);
1092         $data = curl_exec($ch);
1093         if (!curl_errno($ch)) {
1094             $data = trim($data);
1095             if ($data === $teststr) {
1096                 curl_close($ch);
1097                 return INSECURE_DATAROOT_ERROR;
1098             }
1099         }
1100         curl_close($ch);
1101     }
1103     if ($data = @file_get_contents($testurl)) {
1104         $data = trim($data);
1105         if ($data === $teststr) {
1106             return INSECURE_DATAROOT_ERROR;
1107         }
1108     }
1110     preg_match('|https?://([^/]+)|i', $testurl, $matches);
1111     $sitename = $matches[1];
1112     $error = 0;
1113     if ($fp = @fsockopen($sitename, 80, $error)) {
1114         preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
1115         $localurl = $matches[1];
1116         $out = "GET $localurl HTTP/1.1\r\n";
1117         $out .= "Host: $sitename\r\n";
1118         $out .= "Connection: Close\r\n\r\n";
1119         fwrite($fp, $out);
1120         $data = '';
1121         $incoming = false;
1122         while (!feof($fp)) {
1123             if ($incoming) {
1124                 $data .= fgets($fp, 1024);
1125             } else if (@fgets($fp, 1024) === "\r\n") {
1126                 $incoming = true;
1127             }
1128         }
1129         fclose($fp);
1130         $data = trim($data);
1131         if ($data === $teststr) {
1132             return INSECURE_DATAROOT_ERROR;
1133         }
1134     }
1136     return INSECURE_DATAROOT_WARNING;
1139 /// =============================================================================================================
1140 /// administration tree classes and functions
1143 // n.b. documentation is still in progress for this code
1145 /// INTRODUCTION
1147 /// This file performs the following tasks:
1148 ///  -it defines the necessary objects and interfaces to build the Moodle
1149 ///   admin hierarchy
1150 ///  -it defines the admin_externalpage_setup(), admin_externalpage_print_header(),
1151 ///   and admin_externalpage_print_footer() functions used on admin pages
1153 /// ADMIN_SETTING OBJECTS
1155 /// Moodle settings are represented by objects that inherit from the admin_setting
1156 /// class. These objects encapsulate how to read a setting, how to write a new value
1157 /// to a setting, and how to appropriately display the HTML to modify the setting.
1159 /// ADMIN_SETTINGPAGE OBJECTS
1161 /// The admin_setting objects are then grouped into admin_settingpages. The latter
1162 /// appear in the Moodle admin tree block. All interaction with admin_settingpage
1163 /// objects is handled by the admin/settings.php file.
1165 /// ADMIN_EXTERNALPAGE OBJECTS
1167 /// There are some settings in Moodle that are too complex to (efficiently) handle
1168 /// with admin_settingpages. (Consider, for example, user management and displaying
1169 /// lists of users.) In this case, we use the admin_externalpage object. This object
1170 /// places a link to an external PHP file in the admin tree block.
1172 /// If you're using an admin_externalpage object for some settings, you can take
1173 /// advantage of the admin_externalpage_* functions. For example, suppose you wanted
1174 /// to add a foo.php file into admin. First off, you add the following line to
1175 /// admin/settings/first.php (at the end of the file) or to some other file in
1176 /// admin/settings:
1178 ///    $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
1179 ///        $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
1181 /// Next, in foo.php, your file structure would resemble the following:
1183 ///        require_once('.../config.php');
1184 ///        require_once($CFG->libdir.'/adminlib.php');
1185 ///        admin_externalpage_setup('foo');
1186 ///        // functionality like processing form submissions goes here
1187 ///        admin_externalpage_print_header();
1188 ///        // your HTML goes here
1189 ///        admin_externalpage_print_footer();
1191 /// The admin_externalpage_setup() function call ensures the user is logged in,
1192 /// and makes sure that they have the proper role permission to access the page.
1194 /// The admin_externalpage_print_header() function prints the header (it figures
1195 /// out what category and subcategories the page is classified under) and ensures
1196 /// that you're using the admin pagelib (which provides the admin tree block and
1197 /// the admin bookmarks block).
1199 /// The admin_externalpage_print_footer() function properly closes the tables
1200 /// opened up by the admin_externalpage_print_header() function and prints the
1201 /// standard Moodle footer.
1203 /// ADMIN_CATEGORY OBJECTS
1205 /// Above and beyond all this, we have admin_category objects. These objects
1206 /// appear as folders in the admin tree block. They contain admin_settingpage's,
1207 /// admin_externalpage's, and other admin_category's.
1209 /// OTHER NOTES
1211 /// admin_settingpage's, admin_externalpage's, and admin_category's all inherit
1212 /// from part_of_admin_tree (a pseudointerface). This interface insists that
1213 /// a class has a check_access method for access permissions, a locate method
1214 /// used to find a specific node in the admin tree and find parent path.
1216 /// admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
1217 /// interface ensures that the class implements a recursive add function which
1218 /// accepts a part_of_admin_tree object and searches for the proper place to
1219 /// put it. parentable_part_of_admin_tree implies part_of_admin_tree.
1221 /// Please note that the $this->name field of any part_of_admin_tree must be
1222 /// UNIQUE throughout the ENTIRE admin tree.
1224 /// The $this->name field of an admin_setting object (which is *not* part_of_
1225 /// admin_tree) must be unique on the respective admin_settingpage where it is
1226 /// used.
1229 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
1231 /**
1232  * Pseudointerface for anything appearing in the admin tree
1233  *
1234  * The pseudointerface that is implemented by anything that appears in the admin tree
1235  * block. It forces inheriting classes to define a method for checking user permissions
1236  * and methods for finding something in the admin tree.
1237  *
1238  * @author Vincenzo K. Marcovecchio
1239  * @package admin
1240  */
1241 class part_of_admin_tree {
1243     /**
1244      * Finds a named part_of_admin_tree.
1245      *
1246      * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
1247      * and not parentable_part_of_admin_tree, then this function should only check if
1248      * $this->name matches $name. If it does, it should return a reference to $this,
1249      * otherwise, it should return a reference to NULL.
1250      *
1251      * If a class inherits parentable_part_of_admin_tree, this method should be called
1252      * recursively on all child objects (assuming, of course, the parent object's name
1253      * doesn't match the search criterion).
1254      *
1255      * @param string $name The internal name of the part_of_admin_tree we're searching for.
1256      * @return mixed An object reference or a NULL reference.
1257      */
1258     function &locate($name) {
1259         trigger_error('Admin class does not implement method <strong>locate()</strong>', E_USER_WARNING);
1260         return;
1261     }
1263     /**
1264      * Removes named part_of_admin_tree.
1265      *
1266      * @param string $name The internal name of the part_of_admin_tree we want to remove.
1267      * @return bool success.
1268      */
1269     function prune($name) {
1270         trigger_error('Admin class does not implement method <strong>prune()</strong>', E_USER_WARNING);
1271         return;
1272     }
1274     /**
1275      * Search using query
1276      * @param strin query
1277      * @return mixed array-object structure of found settings and pages
1278      */
1279     function search($query) {
1280         trigger_error('Admin class does not implement method <strong>search()</strong>', E_USER_WARNING);
1281         return;
1282     }
1284     /**
1285      * Verifies current user's access to this part_of_admin_tree.
1286      *
1287      * Used to check if the current user has access to this part of the admin tree or
1288      * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
1289      * then this method is usually just a call to has_capability() in the site context.
1290      *
1291      * If a class inherits parentable_part_of_admin_tree, this method should return the
1292      * logical OR of the return of check_access() on all child objects.
1293      *
1294      * @return bool True if the user has access, false if she doesn't.
1295      */
1296     function check_access() {
1297         trigger_error('Admin class does not implement method <strong>check_access()</strong>', E_USER_WARNING);
1298         return;
1299     }
1301     /**
1302      * Mostly usefull for removing of some parts of the tree in admin tree block.
1303      *
1304      * @return True is hidden from normal list view
1305      */
1306     function is_hidden() {
1307         trigger_error('Admin class does not implement method <strong>is_hidden()</strong>', E_USER_WARNING);
1308         return;
1309     }
1312 /**
1313  * Pseudointerface implemented by any part_of_admin_tree that has children.
1314  *
1315  * The pseudointerface implemented by any part_of_admin_tree that can be a parent
1316  * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
1317  * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
1318  * include an add method for adding other part_of_admin_tree objects as children.
1319  *
1320  * @author Vincenzo K. Marcovecchio
1321  * @package admin
1322  */
1323 class parentable_part_of_admin_tree extends part_of_admin_tree {
1325     /**
1326      * Adds a part_of_admin_tree object to the admin tree.
1327      *
1328      * Used to add a part_of_admin_tree object to this object or a child of this
1329      * object. $something should only be added if $destinationname matches
1330      * $this->name. If it doesn't, add should be called on child objects that are
1331      * also parentable_part_of_admin_tree's.
1332      *
1333      * @param string $destinationname The internal name of the new parent for $something.
1334      * @param part_of_admin_tree &$something The object to be added.
1335      * @return bool True on success, false on failure.
1336      */
1337     function add($destinationname, $something) {
1338         trigger_error('Admin class does not implement method <strong>add()</strong>', E_USER_WARNING);
1339         return;
1340     }
1344 /**
1345  * The object used to represent folders (a.k.a. categories) in the admin tree block.
1346  *
1347  * Each admin_category object contains a number of part_of_admin_tree objects.
1348  *
1349  * @author Vincenzo K. Marcovecchio
1350  * @package admin
1351  */
1352 class admin_category extends parentable_part_of_admin_tree {
1354     /**
1355      * @var mixed An array of part_of_admin_tree objects that are this object's children
1356      */
1357     var $children;
1359     /**
1360      * @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1361      */
1362     var $name;
1364     /**
1365      * @var string The displayed name for this category. Usually obtained through get_string()
1366      */
1367     var $visiblename;
1369     /**
1370      * @var bool Should this category be hidden in admin tree block?
1371      */
1372     var $hidden;
1374     /**
1375      * paths
1376      */
1377     var $path;
1378     var $visiblepath;
1380     /**
1381      * Constructor for an empty admin category
1382      *
1383      * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1384      * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
1385      * @param bool $hidden hide category in admin tree block
1386      */
1387     function admin_category($name, $visiblename, $hidden=false) {
1388         $this->children    = array();
1389         $this->name        = $name;
1390         $this->visiblename = $visiblename;
1391         $this->hidden      = $hidden;
1392     }
1394     /**
1395      * Returns a reference to the part_of_admin_tree object with internal name $name.
1396      *
1397      * @param string $name The internal name of the object we want.
1398      * @param bool $findpath initialize path and visiblepath arrays
1399      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1400      */
1401     function &locate($name, $findpath=false) {
1402         if ($this->name == $name) {
1403             if ($findpath) {
1404                 $this->visiblepath[] = $this->visiblename;
1405                 $this->path[]        = $this->name;
1406             }
1407             return $this;
1408         }
1410         $return = NULL;
1411         foreach($this->children as $childid=>$unused) {
1412             if ($return =& $this->children[$childid]->locate($name, $findpath)) {
1413                 break;
1414             }
1415         }
1417         if (!is_null($return) and $findpath) {
1418             $return->visiblepath[] = $this->visiblename;
1419             $return->path[]        = $this->name;
1420         }
1422         return $return;
1423     }
1425     /**
1426      * Search using query
1427      * @param strin query
1428      * @return mixed array-object structure of found settings and pages
1429      */
1430     function search($query) {
1431         $result = array();
1432         foreach ($this->children as $child) {
1433             $subsearch = $child->search($query);
1434             if (!is_array($subsearch)) {
1435                 debugging('Incorrect search result from '.$child->name);
1436                 continue;
1437             }
1438             $result = array_merge($result, $subsearch);
1439         }
1440         return $result;
1441     }
1443     /**
1444      * Removes part_of_admin_tree object with internal name $name.
1445      *
1446      * @param string $name The internal name of the object we want to remove.
1447      * @return bool success
1448      */
1449     function prune($name) {
1451         if ($this->name == $name) {
1452             return false;  //can not remove itself
1453         }
1455         foreach($this->children as $precedence => $child) {
1456             if ($child->name == $name) {
1457                 // found it!
1458                 unset($this->children[$precedence]);
1459                 return true;
1460             }
1461             if ($this->children[$precedence]->prune($name)) {
1462                 return true;
1463             }
1464         }
1465         return false;
1466     }
1468     /**
1469      * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
1470      *
1471      * @param string $destinationame The internal name of the immediate parent that we want for $something.
1472      * @param mixed $something A part_of_admin_tree or setting instanceto be added.
1473      * @return bool True if successfully added, false if $something can not be added.
1474      */
1475     function add($parentname, $something) {
1476         $parent =& $this->locate($parentname);
1477         if (is_null($parent)) {
1478             debugging('parent does not exist!');
1479             return false;
1480         }
1482         if (is_a($something, 'part_of_admin_tree')) {
1483             if (!is_a($parent, 'parentable_part_of_admin_tree')) {
1484                 debugging('error - parts of tree can be inserted only into parentable parts');
1485                 return false;
1486             }
1487             $parent->children[] = $something;
1488             return true;
1490         } else {
1491             debugging('error - can not add this element');
1492             return false;
1493         }
1495     }
1497     /**
1498      * Checks if the user has access to anything in this category.
1499      *
1500      * @return bool True if the user has access to atleast one child in this category, false otherwise.
1501      */
1502     function check_access() {
1503         foreach ($this->children as $child) {
1504             if ($child->check_access()) {
1505                 return true;
1506             }
1507         }
1508         return false;
1509     }
1511     /**
1512      * Is this category hidden in admin tree block?
1513      *
1514      * @return bool True if hidden
1515      */
1516     function is_hidden() {
1517         return $this->hidden;
1518     }
1521 class admin_root extends admin_category {
1522     /**
1523      * list of errors
1524      */
1525     var $errors;
1527     /**
1528      * search query
1529      */
1530     var $search;
1532     /**
1533      * full tree flag - true means all settings required, false onlypages required
1534      */
1535     var $fulltree;
1538     function admin_root() {
1539         parent::admin_category('root', get_string('administration'), false);
1540         $this->errors   = array();
1541         $this->search   = '';
1542         $this->fulltree = true;
1543     }
1546 /**
1547  * Links external PHP pages into the admin tree.
1548  *
1549  * See detailed usage example at the top of this document (adminlib.php)
1550  *
1551  * @author Vincenzo K. Marcovecchio
1552  * @package admin
1553  */
1554 class admin_externalpage extends part_of_admin_tree {
1556     /**
1557      * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
1558      */
1559     var $name;
1561     /**
1562      * @var string The displayed name for this external page. Usually obtained through get_string().
1563      */
1564     var $visiblename;
1566     /**
1567      * @var string The external URL that we should link to when someone requests this external page.
1568      */
1569     var $url;
1571     /**
1572      * @var string The role capability/permission a user must have to access this external page.
1573      */
1574     var $req_capability;
1576     /**
1577      * @var object The context in which capability/permission should be checked, default is site context.
1578      */
1579     var $context;
1581     /**
1582      * @var bool hidden in admin tree block.
1583      */
1584     var $hidden;
1586     /**
1587      * visible path
1588      */
1589     var $path;
1590     var $visiblepath;
1592     /**
1593      * Constructor for adding an external page into the admin tree.
1594      *
1595      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1596      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1597      * @param string $url The external URL that we should link to when someone requests this external page.
1598      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1599      */
1600     function admin_externalpage($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1601         $this->name        = $name;
1602         $this->visiblename = $visiblename;
1603         $this->url         = $url;
1604         if (is_array($req_capability)) {
1605             $this->req_capability = $req_capability;
1606         } else {
1607             $this->req_capability = array($req_capability);
1608         }
1609         $this->hidden  = $hidden;
1610         $this->context = $context;
1611     }
1613     /**
1614      * Returns a reference to the part_of_admin_tree object with internal name $name.
1615      *
1616      * @param string $name The internal name of the object we want.
1617      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1618      */
1619     function &locate($name, $findpath=false) {
1620         if ($this->name == $name) {
1621             if ($findpath) {
1622                 $this->visiblepath = array($this->visiblename);
1623                 $this->path        = array($this->name);
1624             }
1625             return $this;
1626         } else {
1627             $return = NULL;
1628             return $return;
1629         }
1630     }
1632     function prune($name) {
1633         return false;
1634     }
1636     /**
1637      * Search using query
1638      * @param strin query
1639      * @return mixed array-object structure of found settings and pages
1640      */
1641     function search($query) {
1642         $textlib = textlib_get_instance();
1644         $found = false;
1645         if (strpos(strtolower($this->name), $query) !== false) {
1646             $found = true;
1647         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1648             $found = true;
1649         }
1650         if ($found) {
1651             $result = new object();
1652             $result->page     = $this;
1653             $result->settings = array();
1654             return array($this->name => $result);
1655         } else {
1656             return array();
1657         }
1658     }
1660     /**
1661      * Determines if the current user has access to this external page based on $this->req_capability.
1662      * @return bool True if user has access, false otherwise.
1663      */
1664     function check_access() {
1665         if (!get_site()) {
1666             return true; // no access check before site is fully set up
1667         }
1668         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
1669         foreach($this->req_capability as $cap) {
1670             if (has_capability($cap, $context)) {
1671                 return true;
1672             }
1673         }
1674         return false;
1675     }
1677     /**
1678      * Is this external page hidden in admin tree block?
1679      *
1680      * @return bool True if hidden
1681      */
1682     function is_hidden() {
1683         return $this->hidden;
1684     }
1688 /**
1689  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1690  *
1691  * @author Vincenzo K. Marcovecchio
1692  * @package admin
1693  */
1694 class admin_settingpage extends part_of_admin_tree {
1696     /**
1697      * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
1698      */
1699     var $name;
1701     /**
1702      * @var string The displayed name for this external page. Usually obtained through get_string().
1703      */
1704     var $visiblename;
1705     /**
1706      * @var mixed An array of admin_setting objects that are part of this setting page.
1707      */
1708     var $settings;
1710     /**
1711      * @var string The role capability/permission a user must have to access this external page.
1712      */
1713     var $req_capability;
1715     /**
1716      * @var object The context in which capability/permission should be checked, default is site context.
1717      */
1718     var $context;
1720     /**
1721      * @var bool hidden in admin tree block.
1722      */
1723     var $hidden;
1725     /**
1726      * paths
1727      */
1728     var $path;
1729     var $visiblepath;
1731     // see admin_externalpage
1732     function admin_settingpage($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1733         $this->settings    = new object();
1734         $this->name        = $name;
1735         $this->visiblename = $visiblename;
1736         if (is_array($req_capability)) {
1737             $this->req_capability = $req_capability;
1738         } else {
1739             $this->req_capability = array($req_capability);
1740         }
1741         $this->hidden      = $hidden;
1742         $this->context     = $context;
1743     }
1745     // see admin_category
1746     function &locate($name, $findpath=false) {
1747         if ($this->name == $name) {
1748             if ($findpath) {
1749                 $this->visiblepath = array($this->visiblename);
1750                 $this->path        = array($this->name);
1751             }
1752             return $this;
1753         } else {
1754             $return = NULL;
1755             return $return;
1756         }
1757     }
1759     function search($query) {
1760         $found = array();
1762         foreach ($this->settings as $setting) {
1763             if ($setting->is_related($query)) {
1764                 $found[] = $setting;
1765             }
1766         }
1768         if ($found) {
1769             $result = new object();
1770             $result->page     = $this;
1771             $result->settings = $found;
1772             return array($this->name => $result);
1773         }
1775         $textlib = textlib_get_instance();
1777         $found = false;
1778         if (strpos(strtolower($this->name), $query) !== false) {
1779             $found = true;
1780         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1781             $found = true;
1782         }
1783         if ($found) {
1784             $result = new object();
1785             $result->page     = $this;
1786             $result->settings = array();
1787             return array($this->name => $result);
1788         } else {
1789             return array();
1790         }
1791     }
1793     function prune($name) {
1794         return false;
1795     }
1797     /**
1798      * 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
1799      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1800      * @param object $setting is the admin_setting object you want to add
1801      * @return true if successful, false if not
1802      */
1803     function add($setting) {
1804         if (!is_a($setting, 'admin_setting')) {
1805             debugging('error - not a setting instance');
1806             return false;
1807         }
1809         $this->settings->{$setting->name} = $setting;
1810         return true;
1811     }
1813     // see admin_externalpage
1814     function check_access() {
1815         if (!get_site()) {
1816             return true; // no access check before site is fully set up
1817         }
1818         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
1819         foreach($this->req_capability as $cap) {
1820             if (has_capability($cap, $context)) {
1821                 return true;
1822             }
1823         }
1824         return false;
1825     }
1827     /**
1828      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1829      * returns a string of the html
1830      */
1831     function output_html() {
1832         $adminroot =& admin_get_root();
1833         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
1834         foreach($this->settings as $setting) {
1835             $fullname = $setting->get_full_name();
1836             if (array_key_exists($fullname, $adminroot->errors)) {
1837                 $data = $adminroot->errors[$fullname]->data;
1838             } else {
1839                 $data = $setting->get_setting();
1840                 // do not use defaults if settings not available - upgrdesettings handles the defaults!
1841             }
1842             $return .= $setting->output_html($data);
1843         }
1844         $return .= '</fieldset>';
1845         return $return;
1846     }
1848     /**
1849      * Is this settigns page hidden in admin tree block?
1850      *
1851      * @return bool True if hidden
1852      */
1853     function is_hidden() {
1854         return $this->hidden;
1855     }
1860 /**
1861  * Admin settings class. Only exists on setting pages.
1862  * Read & write happens at this level; no authentication.
1863  */
1864 class admin_setting {
1866     var $name;
1867     var $visiblename;
1868     var $description;
1869     var $defaultsetting;
1870     var $updatedcallback;
1871     var $plugin; // null means main config table
1873     /**
1874      * Constructor
1875      * @param $name string unique ascii name
1876      * @param $visiblename string localised name
1877      * @param strin $description localised long description
1878      * @param mixed $defaultsetting string or array depending on implementation
1879      */
1880     function admin_setting($name, $visiblename, $description, $defaultsetting) {
1881         $this->name           = $name;
1882         $this->visiblename    = $visiblename;
1883         $this->description    = $description;
1884         $this->defaultsetting = $defaultsetting;
1885     }
1887     function get_full_name() {
1888         return 's_'.$this->plugin.'_'.$this->name;
1889     }
1891     function get_id() {
1892         return 'id_s_'.$this->plugin.'_'.$this->name;
1893     }
1895     function config_read($name) {
1896         global $CFG;
1897         if ($this->plugin === 'backup') {
1898             require_once($CFG->dirroot.'/backup/lib.php');
1899             $backupconfig = backup_get_config();
1900             if (isset($backupconfig->$name)) {
1901                 return $backupconfig->$name;
1902             } else {
1903                 return NULL;
1904             }
1906         } else if (!empty($this->plugin)) {
1907             $value = get_config($this->plugin, $name);
1908             return $value === false ? NULL : $value;
1910         } else {
1911             if (isset($CFG->$name)) {
1912                 return $CFG->$name;
1913             } else {
1914                 return NULL;
1915             }
1916         }
1917     }
1919     function config_write($name, $value) {
1920         global $CFG;
1921         if ($this->plugin === 'backup') {
1922             require_once($CFG->dirroot.'/backup/lib.php');
1923             return (boolean)backup_set_config($name, $value);
1924         } else {
1925             return (boolean)set_config($name, $value, $this->plugin);
1926         }
1927     }
1929     /**
1930      * Returns current value of this setting
1931      * @return mixed array or string depending on instance, NULL means not set yet
1932      */
1933     function get_setting() {
1934         // has to be overridden
1935         return NULL;
1936     }
1938     /**
1939      * Returns default setting if exists
1940      * @return mixed array or string depending on instance; NULL means no default, user must supply
1941      */
1942     function get_defaultsetting() {
1943         return $this->defaultsetting;
1944     }
1946     /**
1947      * Store new setting
1948      * @param mixed string or array, must not be NULL
1949      * @return '' if ok, string error message otherwise
1950      */
1951     function write_setting($data) {
1952         // should be overridden
1953         return '';
1954     }
1956     /**
1957      * Return part of form with setting
1958      * @param mixed data array or string depending on setting
1959      * @return string
1960      */
1961     function output_html($data, $query='') {
1962         // should be overridden
1963         return;
1964     }
1966     /**
1967      * function called if setting updated - cleanup, cache reset, etc.
1968      */
1969     function set_updatedcallback($functionname) {
1970         $this->updatedcallback = $functionname;
1971     }
1973     /**
1974      * Is setting related to query text - used when searching
1975      * @param string $query
1976      * @return bool
1977      */
1978     function is_related($query) {
1979         if (strpos(strtolower($this->name), $query) !== false) {
1980             return true;
1981         }
1982         $textlib = textlib_get_instance();
1983         if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1984             return true;
1985         }
1986         if (strpos($textlib->strtolower($this->description), $query) !== false) {
1987             return true;
1988         }
1989         $current = $this->get_setting();
1990         if (!is_null($current)) {
1991             if (is_string($current)) {
1992                 if (strpos($textlib->strtolower($current), $query) !== false) {
1993                     return true;
1994                 }
1995             }
1996         }
1997         $default = $this->get_defaultsetting();
1998         if (!is_null($default)) {
1999             if (is_string($default)) {
2000                 if (strpos($textlib->strtolower($default), $query) !== false) {
2001                     return true;
2002                 }
2003             }
2004         }
2005         return false;
2006     }
2009 /**
2010  * No setting - just heading and text.
2011  */
2012 class admin_setting_heading extends admin_setting {
2013     /**
2014      * not a setting, just text
2015      * @param string $name of setting
2016      * @param string $heading heading
2017      * @param string $information text in box
2018      */
2019     function admin_setting_heading($name, $heading, $information) {
2020         parent::admin_setting($name, $heading, $information, '');
2021     }
2023     function get_setting() {
2024         return true;
2025     }
2027     function get_defaultsetting() {
2028         return true;
2029     }
2031     function write_setting($data) {
2032         // do not write any setting
2033         return '';
2034     }
2036     function output_html($data, $query='') {
2037         $return = '';
2038         if ($this->visiblename != '') {
2039             $return .= print_heading('<a name="'.$this->name.'">'.highlightfast($query, $this->visiblename).'</a>', '', 3, 'main', true);
2040         }
2041         if ($this->description != '') {
2042             $return .= print_box(highlight($query, $this->description), 'generalbox formsettingheading', '', true);
2043         }
2044         return $return;
2045     }
2048 /**
2049  * The most flexibly setting, user is typing text
2050  */
2051 class admin_setting_configtext extends admin_setting {
2053     var $paramtype;
2054     var $size;
2056     /**
2057      * config text contructor
2058      * @param string $name of setting
2059      * @param string $visiblename localised
2060      * @param string $description long localised info
2061      * @param string $defaultsetting
2062      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2063      * @param int $size default field size
2064      */
2065     function admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2066         $this->paramtype = $paramtype;
2067         if (!is_null($size)) {
2068             $this->size  = $size;
2069         } else {
2070             $this->size  = ($paramtype == PARAM_INT) ? 5 : 30;
2071         }
2072         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2073     }
2075     function get_setting() {
2076         return $this->config_read($this->name);
2077     }
2079     function write_setting($data) {
2080         if ($this->paramtype === PARAM_INT and $data === '') {
2081             // do not complain if '' used instead of 0
2082             $data = 0;
2083         }
2084         // $data is a string
2085         $validated = $this->validate($data);
2086         if ($validated !== true) {
2087             return $validated;
2088         }
2089         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2090     }
2092     /**
2093      * Validate data before storage
2094      * @param string data
2095      * @return mixed true if ok string if error found
2096      */
2097     function validate($data) {
2098         if (is_string($this->paramtype)) {
2099             if (preg_match($this->paramtype, $data)) {
2100                 return true;
2101             } else {
2102                 return get_string('validateerror', 'admin');
2103             }
2105         } else if ($this->paramtype === PARAM_RAW) {
2106             return true;
2108         } else {
2109             $cleaned = clean_param($data, $this->paramtype);
2110             if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
2111                 return true;
2112             } else {
2113                 return get_string('validateerror', 'admin');
2114             }
2115         }
2116     }
2118     function output_html($data, $query='') {
2119         $default = $this->get_defaultsetting();
2121         return format_admin_setting($this, $this->visiblename,
2122                 '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
2123                 $this->description, true, '', $default, $query);
2124     }
2127 /**
2128  * General text area without html editor.
2129  */
2130 class admin_setting_configtextarea extends admin_setting_configtext {
2131     var $rows;
2132     var $cols;
2134     function admin_setting_configtextarea($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2135         $this->rows = $rows;
2136         $this->cols = $cols;
2137         parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype);
2138     }
2140     function output_html($data, $query='') {
2141         $default = $this->get_defaultsetting();
2143         $defaultinfo = $default;
2144         if (!is_null($default) and $default !== '') {
2145             $defaultinfo = "\n".$default;
2146         }
2148         return format_admin_setting($this, $this->visiblename,
2149                 '<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>',
2150                 $this->description, true, '', $defaultinfo, $query);
2151     }
2154 /**
2155  * Password field, allows unmasking of password
2156  */
2157 class admin_setting_configpasswordunmask extends admin_setting_configtext {
2158     /**
2159      * Constructor
2160      * @param string $name of setting
2161      * @param string $visiblename localised
2162      * @param string $description long localised info
2163      * @param string $defaultsetting default password
2164      */
2165     function admin_setting_configpasswordunmask($name, $visiblename, $description, $defaultsetting) {
2166         parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2167     }
2169     function output_html($data, $query='') {
2170         $id = $this->get_id();
2171         $unmask = get_string('unmaskpassword', 'form');
2172         $unmaskjs = '<script type="text/javascript">
2173 //<![CDATA[
2174 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
2176 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
2178 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
2180 var unmaskchb = document.createElement("input");
2181 unmaskchb.setAttribute("type", "checkbox");
2182 unmaskchb.setAttribute("id", "'.$id.'unmask");
2183 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
2184 unmaskdiv.appendChild(unmaskchb);
2186 var unmasklbl = document.createElement("label");
2187 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
2188 if (is_ie) {
2189   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
2190 } else {
2191   unmasklbl.setAttribute("for", "'.$id.'unmask");
2193 unmaskdiv.appendChild(unmasklbl);
2195 if (is_ie) {
2196   // ugly hack to work around the famous onchange IE bug
2197   unmaskchb.onclick = function() {this.blur();};
2198   unmaskdiv.onclick = function() {this.blur();};
2200 //]]>
2201 </script>';
2202         return format_admin_setting($this, $this->visiblename,
2203                 '<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>',
2204                 $this->description, true, '', NULL, $query);
2205     }
2208 /**
2209  * Path to directory
2210  */
2211 class admin_setting_configfile extends admin_setting_configtext {
2212     /**
2213      * Constructor
2214      * @param string $name of setting
2215      * @param string $visiblename localised
2216      * @param string $description long localised info
2217      * @param string $defaultdirectory default directory location
2218      */
2219     function admin_setting_configfile($name, $visiblename, $description, $defaultdirectory) {
2220         parent::admin_setting_configtext($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2221     }
2223     function output_html($data, $query='') {
2224         $default = $this->get_defaultsetting();
2226         if ($data) {
2227             if (file_exists($data)) {
2228                 $executable = '<span class="pathok">&#x2714;</span>';
2229             } else {
2230                 $executable = '<span class="patherror">&#x2718;</span>';
2231             }
2232         } else {
2233             $executable = '';
2234         }
2236         return format_admin_setting($this, $this->visiblename,
2237                 '<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>',
2238                 $this->description, true, '', $default, $query);
2239     }
2242 /**
2243  * Path to executable file
2244  */
2245 class admin_setting_configexecutable extends admin_setting_configfile {
2247     function output_html($data, $query='') {
2248         $default = $this->get_defaultsetting();
2250         if ($data) {
2251             if (file_exists($data) and is_executable($data)) {
2252                 $executable = '<span class="pathok">&#x2714;</span>';
2253             } else {
2254                 $executable = '<span class="patherror">&#x2718;</span>';
2255             }
2256         } else {
2257             $executable = '';
2258         }
2260         return format_admin_setting($this, $this->visiblename,
2261                 '<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>',
2262                 $this->description, true, '', $default, $query);
2263     }
2266 /**
2267  * Path to directory
2268  */
2269 class admin_setting_configdirectory extends admin_setting_configfile {
2270     function output_html($data, $query='') {
2271         $default = $this->get_defaultsetting();
2273         if ($data) {
2274             if (file_exists($data) and is_dir($data)) {
2275                 $executable = '<span class="pathok">&#x2714;</span>';
2276             } else {
2277                 $executable = '<span class="patherror">&#x2718;</span>';
2278             }
2279         } else {
2280             $executable = '';
2281         }
2283         return format_admin_setting($this, $this->visiblename,
2284                 '<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>',
2285                 $this->description, true, '', $default, $query);
2286     }
2289 /**
2290  * Checkbox
2291  */
2292 class admin_setting_configcheckbox extends admin_setting {
2293     var $yes;
2294     var $no;
2296     /**
2297      * Constructor
2298      * @param string $name of setting
2299      * @param string $visiblename localised
2300      * @param string $description long localised info
2301      * @param string $defaultsetting
2302      * @param string $yes value used when checked
2303      * @param string $no value used when not checked
2304      */
2305     function admin_setting_configcheckbox($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2306         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2307         $this->yes = (string)$yes;
2308         $this->no  = (string)$no;
2309     }
2311     function get_setting() {
2312         return $this->config_read($this->name);
2313     }
2315     function write_setting($data) {
2316         if ((string)$data === $this->yes) { // convert to strings before comparison
2317             $data = $this->yes;
2318         } else {
2319             $data = $this->no;
2320         }
2321         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2322     }
2324     function output_html($data, $query='') {
2325         $default = $this->get_defaultsetting();
2327         if (!is_null($default)) {
2328             if ((string)$default === $this->yes) {
2329                 $defaultinfo = get_string('checkboxyes', 'admin');
2330             } else {
2331                 $defaultinfo = get_string('checkboxno', 'admin');
2332             }
2333         } else {
2334             $defaultinfo = NULL;
2335         }
2337         if ((string)$data === $this->yes) { // convert to strings before comparison
2338             $checked = 'checked="checked"';
2339         } else {
2340             $checked = '';
2341         }
2343         return format_admin_setting($this, $this->visiblename,
2344                 '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2345                 .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2346                 $this->description, true, '', $defaultinfo, $query);
2347     }
2350 /**
2351  * Multiple checkboxes, each represents different value, stored in csv format
2352  */
2353 class admin_setting_configmulticheckbox extends admin_setting {
2354     var $choices;
2356     /**
2357      * Constructor
2358      * @param string $name of setting
2359      * @param string $visiblename localised
2360      * @param string $description long localised info
2361      * @param array $defaultsetting array of selected
2362      * @param array $choices array of $value=>$label for each checkbox
2363      */
2364     function admin_setting_configmulticheckbox($name, $visiblename, $description, $defaultsetting, $choices) {
2365         $this->choices = $choices;
2366         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2367     }
2369     /**
2370      * This function may be used in ancestors for lazy loading of choices
2371      * @return true if loaded, false if error
2372      */
2373     function load_choices() {
2374         /*
2375         if (is_array($this->choices)) {
2376             return true;
2377         }
2378         .... load choices here
2379         */
2380         return true;
2381     }
2383     /**
2384      * Is setting related to query text - used when searching
2385      * @param string $query
2386      * @return bool
2387      */
2388     function is_related($query) {
2389         if (!$this->load_choices() or empty($this->choices)) {
2390             return false;
2391         }
2392         if (parent::is_related($query)) {
2393             return true;
2394         }
2396         $textlib = textlib_get_instance();
2397         foreach ($this->choices as $desc) {
2398             if (strpos($textlib->strtolower($desc), $query) !== false) {
2399                 return true;
2400             }
2401         }
2402         return false;
2403     }
2405     function get_setting() {
2406         $result = $this->config_read($this->name);
2408         if (is_null($result)) {
2409             return NULL;
2410         }
2411         if ($result === '') {
2412             return array();
2413         }
2414         $enabled = explode(',', $result);
2415         $setting = array();
2416         foreach ($enabled as $option) {
2417             $setting[$option] = 1;
2418         }
2419         return $setting;
2420     }
2422     function write_setting($data) {
2423         if (!is_array($data)) {
2424             return ''; // ignore it
2425         }
2426         if (!$this->load_choices() or empty($this->choices)) {
2427             return '';
2428         }
2429         unset($data['xxxxx']);
2430         $result = array();
2431         foreach ($data as $key => $value) {
2432             if ($value and array_key_exists($key, $this->choices)) {
2433                 $result[] = $key;
2434             }
2435         }
2436         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2437     }
2439     function output_html($data, $query='') {
2440         if (!$this->load_choices() or empty($this->choices)) {
2441             return '';
2442         }
2443         $default = $this->get_defaultsetting();
2444         if (is_null($default)) {
2445             $default = array();
2446         }
2447         if (is_null($data)) {
2448             $data = array();
2449         }
2450         $options = array();
2451         $defaults = array();
2452         foreach ($this->choices as $key=>$description) {
2453             if (!empty($data[$key])) {
2454                 $checked = 'checked="checked"';
2455             } else {
2456                 $checked = '';
2457             }
2458             if (!empty($default[$key])) {
2459                 $defaults[] = $description;
2460             }
2462             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
2463                          .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
2464         }
2466         if (is_null($default)) {
2467             $defaultinfo = NULL;
2468         } else if (!empty($defaults)) {
2469             $defaultinfo = implode(', ', $defaults);
2470         } else {
2471             $defaultinfo = get_string('none');
2472         }
2474         $return = '<div class="form-multicheckbox">';
2475         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2476         if ($options) {
2477             $return .= '<ul>';
2478             foreach ($options as $option) {
2479                 $return .= '<li>'.$option.'</li>';
2480             }
2481             $return .= '</ul>';
2482         }
2483         $return .= '</div>';
2485         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2487     }
2490 /**
2491  * Multiple checkboxes 2, value stored as string 00101011
2492  */
2493 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
2494     function get_setting() {
2495         $result = $this->config_read($this->name);
2496         if (is_null($result)) {
2497             return NULL;
2498         }
2499         if (!$this->load_choices()) {
2500             return NULL;
2501         }
2502         $result = str_pad($result, count($this->choices), '0');
2503         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2504         $setting = array();
2505         foreach ($this->choices as $key=>$unused) {
2506             $value = array_shift($result);
2507             if ($value) {
2508                 $setting[$key] = 1;
2509             }
2510         }
2511         return $setting;
2512     }
2514     function write_setting($data) {
2515         if (!is_array($data)) {
2516             return ''; // ignore it
2517         }
2518         if (!$this->load_choices() or empty($this->choices)) {
2519             return '';
2520         }
2521         $result = '';
2522         foreach ($this->choices as $key=>$unused) {
2523             if (!empty($data[$key])) {
2524                 $result .= '1';
2525             } else {
2526                 $result .= '0';
2527             }
2528         }
2529         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2530     }
2533 /**
2534  * Select one value from list
2535  */
2536 class admin_setting_configselect extends admin_setting {
2537     var $choices;
2539     /**
2540      * Constructor
2541      * @param string $name of setting
2542      * @param string $visiblename localised
2543      * @param string $description long localised info
2544      * @param string $defaultsetting
2545      * @param array $choices array of $value=>$label for each selection
2546      */
2547     function admin_setting_configselect($name, $visiblename, $description, $defaultsetting, $choices) {
2548         $this->choices = $choices;
2549         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2550     }
2552     /**
2553      * This function may be used in ancestors for lazy loading of choices
2554      * @return true if loaded, false if error
2555      */
2556     function load_choices() {
2557         /*
2558         if (is_array($this->choices)) {
2559             return true;
2560         }
2561         .... load choices here
2562         */
2563         return true;
2564     }
2566     function is_related($query) {
2567         if (parent::is_related($query)) {
2568             return true;
2569         }
2570         if (!$this->load_choices()) {
2571             return false;
2572         }
2573         $textlib = textlib_get_instance();
2574         foreach ($this->choices as $key=>$value) {
2575             if (strpos($textlib->strtolower($key), $query) !== false) {
2576                 return true;
2577             }
2578             if (strpos($textlib->strtolower($value), $query) !== false) {
2579                 return true;
2580             }
2581         }
2582         return false;
2583     }
2585     function get_setting() {
2586         return $this->config_read($this->name);
2587     }
2589     function write_setting($data) {
2590         if (!$this->load_choices() or empty($this->choices)) {
2591             return '';
2592         }
2593         if (!array_key_exists($data, $this->choices)) {
2594             return ''; // ignore it
2595         }
2597         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2598     }
2600     /**
2601      * Ensure the options are loaded, and generate the HTML for the select
2602      * element and any warning message. Separating this out from output_html
2603      * makes it easier to subclass this class.
2604      *
2605      * @param string $data the option to show as selected.
2606      * @param string $current the currently selected option in the database, null if none.
2607      * @param string $default the default selected option.
2608      * @return array the HTML for the select element, and a warning message.
2609      */
2610     function output_select_html($data, $current, $default, $extraname = '') {
2611         if (!$this->load_choices() or empty($this->choices)) {
2612             return array('', '');
2613         }
2615         $warning = '';
2616         if (is_null($current)) {
2617             // first run
2618         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2619             // no warning
2620         } else if (!array_key_exists($current, $this->choices)) {
2621             $warning = get_string('warningcurrentsetting', 'admin', s($current));
2622             if (!is_null($default) and $data == $current) {
2623                 $data = $default; // use default instead of first value when showing the form
2624             }
2625         }
2627         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
2628         foreach ($this->choices as $key => $value) {
2629             // the string cast is needed because key may be integer - 0 is equal to most strings!
2630             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
2631         }
2632         $selecthtml .= '</select>';
2633         return array($selecthtml, $warning);
2634     }
2636     function output_html($data, $query='') {
2637         $default = $this->get_defaultsetting();
2638         $current = $this->get_setting();
2640         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2641         if (!$selecthtml) {
2642             return '';
2643         }
2645         if (!is_null($default) and array_key_exists($default, $this->choices)) {
2646             $defaultinfo = $this->choices[$default];
2647         } else {
2648             $defaultinfo = NULL;
2649         }
2651         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
2653         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
2654     }
2657 /**
2658  * Select multiple items from list
2659  */
2660 class admin_setting_configmultiselect extends admin_setting_configselect {
2661     /**
2662      * Constructor
2663      * @param string $name of setting
2664      * @param string $visiblename localised
2665      * @param string $description long localised info
2666      * @param array $defaultsetting array of selected items
2667      * @param array $choices array of $value=>$label for each list item
2668      */
2669     function admin_setting_configmultiselect($name, $visiblename, $description, $defaultsetting, $choices) {
2670         parent::admin_setting_configselect($name, $visiblename, $description, $defaultsetting, $choices);
2671     }
2673     function get_setting() {
2674         $result = $this->config_read($this->name);
2675         if (is_null($result)) {
2676             return NULL;
2677         }
2678         if ($result === '') {
2679             return array();
2680         }
2681         return explode(',', $result);
2682     }
2684     function write_setting($data) {
2685         if (!is_array($data)) {
2686             return ''; //ignore it
2687         }
2688         if (!$this->load_choices() or empty($this->choices)) {
2689             return '';
2690         }
2692         unset($data['xxxxx']);
2694         $save = array();
2695         foreach ($data as $value) {
2696             if (!array_key_exists($value, $this->choices)) {
2697                 continue; // ignore it
2698             }
2699             $save[] = $value;
2700         }
2702         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2703     }
2705     /**
2706      * Is setting related to query text - used when searching
2707      * @param string $query
2708      * @return bool
2709      */
2710     function is_related($query) {
2711         if (!$this->load_choices() or empty($this->choices)) {
2712             return false;
2713         }
2714         if (parent::is_related($query)) {
2715             return true;
2716         }
2718         $textlib = textlib_get_instance();
2719         foreach ($this->choices as $desc) {
2720             if (strpos($textlib->strtolower($desc), $query) !== false) {
2721                 return true;
2722             }
2723         }
2724         return false;
2725     }
2727     function output_html($data, $query='') {
2728         if (!$this->load_choices() or empty($this->choices)) {
2729             return '';
2730         }
2731         $choices = $this->choices;
2732         $default = $this->get_defaultsetting();
2733         if (is_null($default)) {
2734             $default = array();
2735         }
2736         if (is_null($data)) {
2737             $data = array();
2738         }
2740         $defaults = array();
2741         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2742         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="10" multiple="multiple">';
2743         foreach ($this->choices as $key => $description) {
2744             if (in_array($key, $data)) {
2745                 $selected = 'selected="selected"';
2746             } else {
2747                 $selected = '';
2748             }
2749             if (in_array($key, $default)) {
2750                 $defaults[] = $description;
2751             }
2753             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2754         }
2756         if (is_null($default)) {
2757             $defaultinfo = NULL;
2758         } if (!empty($defaults)) {
2759             $defaultinfo = implode(', ', $defaults);
2760         } else {
2761             $defaultinfo = get_string('none');
2762         }
2764         $return .= '</select></div>';
2765         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
2766     }
2769 /**
2770  * Time selector
2771  * this is a liiitle bit messy. we're using two selects, but we're returning
2772  * them as an array named after $name (so we only use $name2 internally for the setting)
2773  */
2774 class admin_setting_configtime extends admin_setting {
2775     var $name2;
2777     /**
2778      * Constructor
2779      * @param string $hoursname setting for hours
2780      * @param string $minutesname setting for hours
2781      * @param string $visiblename localised
2782      * @param string $description long localised info
2783      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2784      */
2785     function admin_setting_configtime($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
2786         $this->name2 = $minutesname;
2787         parent::admin_setting($hoursname, $visiblename, $description, $defaultsetting);
2788     }
2790     function get_setting() {
2791         $result1 = $this->config_read($this->name);
2792         $result2 = $this->config_read($this->name2);
2793         if (is_null($result1) or is_null($result2)) {
2794             return NULL;
2795         }
2797         return array('h' => $result1, 'm' => $result2);
2798     }
2800     function write_setting($data) {
2801         if (!is_array($data)) {
2802             return '';
2803         }
2805         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2806         return ($result ? '' : get_string('errorsetting', 'admin'));
2807     }
2809     function output_html($data, $query='') {
2810         $default = $this->get_defaultsetting();
2812         if (is_array($default)) {
2813             $defaultinfo = $default['h'].':'.$default['m'];
2814         } else {
2815             $defaultinfo = NULL;
2816         }
2818         $return = '<div class="form-time defaultsnext">'.
2819                   '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
2820         for ($i = 0; $i < 24; $i++) {
2821             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2822         }
2823         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2824         for ($i = 0; $i < 60; $i += 5) {
2825             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2826         }
2827         $return .= '</select></div>';
2828         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
2829     }
2833 class admin_setting_configiplist extends admin_setting_configtextarea {
2834     function validate($data) {
2835         if(!empty($data)) {
2836             $ips = explode("\n", $data);
2837         } else {
2838             return true;
2839         }
2840         $result = true;
2841         foreach($ips as $ip) {
2842             $ip = trim($ip);
2843             if(preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
2844                    preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
2845                    preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
2846                 $result = true;
2847             } else {
2848                 $result = false;
2849                 break;
2850             }
2851         }
2852         if($result){
2853             return true;
2854         } else {
2855             return get_string('validateerror', 'admin');
2856         }
2857     }
2860 /**
2861  * Special checkbox for calendar - resets SESSION vars.
2862  */
2863 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
2864     function admin_setting_special_adminseesall() {
2865         parent::admin_setting_configcheckbox('calendar_adminseesall', get_string('adminseesall', 'admin'),
2866                                              get_string('helpadminseesall', 'admin'), '0');
2867     }
2869     function write_setting($data) {
2870         global $SESSION;
2871         unset($SESSION->cal_courses_shown);
2872         return parent::write_setting($data);
2873     }
2876 /**
2877  * Special select for settings that are altered in setup.php and can not be altered on the fly
2878  */
2879 class admin_setting_special_selectsetup extends admin_setting_configselect {
2880     function get_setting() {
2881         // read directly from db!
2882         return get_config(NULL, $this->name);
2883     }
2885     function write_setting($data) {
2886         global $CFG;
2887         // do not change active CFG setting!
2888         $current = $CFG->{$this->name};
2889         $result = parent::write_setting($data);
2890         $CFG->{$this->name} = $current;
2891         return $result;
2892     }
2895 /**
2896  * Special select for frontpage - stores data in course table
2897  */
2898 class admin_setting_sitesetselect extends admin_setting_configselect {
2899     function get_setting() {
2900         $site = get_site();
2901         return $site->{$this->name};
2902     }
2904     function write_setting($data) {
2905         global $DB;
2906         if (!in_array($data, array_keys($this->choices))) {
2907             return get_string('errorsetting', 'admin');
2908         }
2909         $record = new stdClass();
2910         $record->id           = SITEID;
2911         $temp                 = $this->name;
2912         $record->$temp        = $data;
2913         $record->timemodified = time();
2914         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
2915     }
2918 /**
2919  * Special select - lists on the frontpage - hacky
2920  */
2921 class admin_setting_courselist_frontpage extends admin_setting {
2922     var $choices;
2924     function admin_setting_courselist_frontpage($loggedin) {
2925         global $CFG;
2926         require_once($CFG->dirroot.'/course/lib.php');
2927         $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
2928         $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
2929         $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
2930         $defaults    = array(FRONTPAGECOURSELIST);
2931         parent::admin_setting($name, $visiblename, $description, $defaults);
2932     }
2934     function load_choices() {
2935         global $DB;
2936         if (is_array($this->choices)) {
2937             return true;
2938         }
2939         $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
2940                                FRONTPAGECOURSELIST    => get_string('frontpagecourselist'),
2941                                FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
2942                                FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
2943                                'none'                 => get_string('none'));
2944         if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
2945             unset($this->choices[FRONTPAGECOURSELIST]);
2946         }
2947         return true;
2948     }
2949     function get_setting() {
2950         $result = $this->config_read($this->name);
2951         if (is_null($result)) {
2952             return NULL;
2953         }
2954         if ($result === '') {
2955             return array();
2956         }
2957         return explode(',', $result);
2958     }
2960     function write_setting($data) {
2961         if (!is_array($data)) {
2962             return '';
2963         }
2964         $this->load_choices();
2965         $save = array();
2966         foreach($data as $datum) {
2967             if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
2968                 continue;
2969             }
2970             $save[$datum] = $datum; // no duplicates
2971         }
2972         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2973     }
2975     function output_html($data, $query='') {
2976         $this->load_choices();
2977         $currentsetting = array();
2978         foreach ($data as $key) {
2979             if ($key != 'none' and array_key_exists($key, $this->choices)) {
2980                 $currentsetting[] = $key; // already selected first
2981             }
2982         }
2984         $return = '<div class="form-group">';
2985         for ($i = 0; $i < count($this->choices) - 1; $i++) {
2986             if (!array_key_exists($i, $currentsetting)) {
2987                 $currentsetting[$i] = 'none'; //none
2988             }
2989             $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
2990             foreach ($this->choices as $key => $value) {
2991                 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
2992             }
2993             $return .= '</select>';
2994             if ($i !== count($this->choices) - 2) {
2995                 $return .= '<br />';
2996             }
2997         }
2998         $return .= '</div>';
3000         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3001     }
3004 /**
3005  * Special checkbox for frontpage - stores data in course table
3006  */
3007 class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
3008     function get_setting() {
3009         $site = get_site();
3010         return $site->{$this->name};
3011     }
3013     function write_setting($data) {
3014         global $DB;
3015         $record = new object();
3016         $record->id            = SITEID;
3017         $record->{$this->name} = ($data == '1' ? 1 : 0);
3018         $record->timemodified  = time();
3019         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3020     }
3023 /**
3024  * Special text for frontpage - stores data in course table.
3025  * Empty string means not set here. Manual setting is required.
3026  */
3027 class admin_setting_sitesettext extends admin_setting_configtext {
3028     function get_setting() {
3029         $site = get_site();
3030         return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
3031     }
3033     function validate($data) {
3034         $cleaned = clean_param($data, PARAM_MULTILANG);
3035         if ($cleaned === '') {
3036             return get_string('required');
3037         }
3038         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
3039             return true;
3040         } else {
3041             return get_string('validateerror', 'admin');
3042         }
3043     }
3045     function write_setting($data) {
3046         global $DB;
3047         $data = trim($data);
3048         $validated = $this->validate($data);
3049         if ($validated !== true) {
3050             return $validated;
3051         }
3053         $record = new object();
3054         $record->id            = SITEID;
3055         $record->{$this->name} = $data;
3056         $record->timemodified  = time();
3057         return ($DB->update_record('course', $record) ? '' : get_string('dbupdatefailed', 'error'));
3058     }
3061 /**
3062  * Special text editor for site description.
3063  */
3064 class admin_setting_special_frontpagedesc extends admin_setting {
3065     function admin_setting_special_frontpagedesc() {
3066         parent::admin_setting('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
3067     }
3069     function get_setting() {
3070         $site = get_site();
3071         return $site->{$this->name};
3072     }
3074     function write_setting($data) {
3075         global $DB;
3076         $record = new object();
3077         $record->id            = SITEID;
3078         $record->{$this->name} = $data;
3079         $record->timemodified  = time();
3080         return($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3081     }
3083     function output_html($data, $query='') {
3084         global $CFG;
3086         $CFG->adminusehtmleditor = can_use_html_editor();
3087         $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
3089         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3090     }
3093 class admin_setting_special_editorfontlist extends admin_setting {
3095     var $items;
3097     function admin_setting_special_editorfontlist() {
3098         global $CFG;
3099         $name = 'editorfontlist';
3100         $visiblename = get_string('editorfontlist', 'admin');
3101         $description = get_string('configeditorfontlist', 'admin');
3102         $defaults = array('k0' => 'Trebuchet',
3103                           'v0' => 'Trebuchet MS,Verdana,Arial,Helvetica,sans-serif',
3104                           'k1' => 'Arial',
3105                           'v1' => 'arial,helvetica,sans-serif',
3106                           'k2' => 'Courier New',
3107                           'v2' => 'courier new,courier,monospace',
3108                           'k3' => 'Georgia',
3109                           'v3' => 'georgia,times new roman,times,serif',
3110                           'k4' => 'Tahoma',
3111                           'v4' => 'tahoma,arial,helvetica,sans-serif',
3112                           'k5' => 'Times New Roman',
3113                           'v5' => 'times new roman,times,serif',
3114                           'k6' => 'Verdana',
3115                           'v6' => 'verdana,arial,helvetica,sans-serif',
3116                           'k7' => 'Impact',
3117                           'v7' => 'impact',
3118                           'k8' => 'Wingdings',
3119                           'v8' => 'wingdings');
3120         parent::admin_setting($name, $visiblename, $description, $defaults);
3121     }
3123     function get_setting() {
3124         global $CFG;
3125         $result = $this->config_read($this->name);
3126         if (is_null($result)) {
3127             return NULL;
3128         }
3129         $i = 0;
3130         $currentsetting = array();
3131         $items = explode(';', $result);
3132         foreach ($items as $item) {
3133           $item = explode(':', $item);
3134           $currentsetting['k'.$i] = $item[0];
3135           $currentsetting['v'.$i] = $item[1];
3136           $i++;
3137         }
3138         return $currentsetting;
3139     }
3141     function write_setting($data) {
3143         // there miiight be an easier way to do this :)
3144         // if this is changed, make sure the $defaults array above is modified so that this
3145         // function processes it correctly
3147         $keys = array();
3148         $values = array();
3150         foreach ($data as $key => $value) {
3151             if (substr($key,0,1) == 'k') {
3152                 $keys[substr($key,1)] = $value;
3153             } elseif (substr($key,0,1) == 'v') {
3154                 $values[substr($key,1)] = $value;
3155             }
3156         }
3158         $result = array();
3159         for ($i = 0; $i < count($keys); $i++) {
3160             if (($keys[$i] !== '') && ($values[$i] !== '')) {
3161                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).':'.clean_param($values[$i], PARAM_NOTAGS);
3162             }
3163         }
3165         return ($this->config_write($this->name, implode(';', $result)) ? '' : get_string('errorsetting', 'admin'));
3166     }
3168     function output_html($data, $query='') {
3169         $fullname = $this->get_full_name();
3170         $return = '<div class="form-group">';
3171         for ($i = 0; $i < count($data) / 2; $i++) {
3172             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
3173             $return .= '&nbsp;&nbsp;';
3174             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
3175         }
3176         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
3177         $return .= '&nbsp;&nbsp;';
3178         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3179         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
3180         $return .= '&nbsp;&nbsp;';
3181         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
3182         $return .= '</div>';
3184         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3185     }
3189 class admin_setting_emoticons extends admin_setting {
3191     var $items;
3193     function admin_setting_emoticons() {
3194         global $CFG;
3195         $name = 'emoticons';
3196         $visiblename = get_string('emoticons', 'admin');
3197         $description = get_string('configemoticons', 'admin');
3198         $defaults = array('k0' => ':-)',
3199                           'v0' => 'smiley',
3200                           'k1' => ':)',
3201                           'v1' => 'smiley',
3202                           'k2' => ':-D',
3203                           'v2' => 'biggrin',
3204                           'k3' => ';-)',
3205                           'v3' => 'wink',
3206                           'k4' => ':-/',
3207                           'v4' => 'mixed',
3208                           'k5' => 'V-.',
3209                           'v5' => 'thoughtful',
3210                           'k6' => ':-P',
3211                           'v6' => 'tongueout',
3212                           'k7' => 'B-)',
3213                           'v7' => 'cool',
3214                           'k8' => '^-)',
3215                           'v8' => 'approve',
3216                           'k9' => '8-)',
3217                           'v9' => 'wideeyes',
3218                           'k10' => ':o)',
3219                           'v10' => 'clown',
3220                           'k11' => ':-(',
3221                           'v11' => 'sad',
3222                           'k12' => ':(',
3223                           'v12' => 'sad',
3224                           'k13' => '8-.',
3225                           'v13' => 'shy',
3226                           'k14' => ':-I',
3227                           'v14' => 'blush',
3228                           'k15' => ':-X',
3229                           'v15' => 'kiss',
3230                           'k16' => '8-o',
3231                           'v16' => 'surprise',
3232                           'k17' => 'P-|',
3233                           'v17' => 'blackeye',
3234                           'k18' => '8-[',
3235                           'v18' => 'angry',
3236                           'k19' => 'xx-P',
3237                           'v19' => 'dead',
3238                           'k20' => '|-.',
3239                           'v20' => 'sleepy',
3240                           'k21' => '}-]',
3241                           'v21' => 'evil',
3242                           'k22' => '(h)',
3243                           'v22' => 'heart',
3244                           'k23' => '(heart)',
3245                           'v23' => 'heart',
3246                           'k24' => '(y)',
3247                           'v24' => 'yes',
3248                           'k25' => '(n)',
3249                           'v25' => 'no',
3250                           'k26' => '(martin)',
3251                           'v26' => 'martin',
3252                           'k27' => '( )',
3253                           'v27' => 'egg');
3254         parent::admin_setting($name, $visiblename, $description, $defaults);
3255     }
3257     function get_setting() {
3258         global $CFG;
3259         $result = $this->config_read($this->name);
3260         if (is_null($result)) {
3261             return NULL;
3262         }
3263         $i = 0;
3264         $currentsetting = array();
3265         $items = explode('{;}', $result);
3266         foreach ($items as $item) {
3267           $item = explode('{:}', $item);
3268           $currentsetting['k'.$i] = $item[0];
3269           $currentsetting['v'.$i] = $item[1];
3270           $i++;
3271         }
3272         return $currentsetting;
3273     }
3275     function write_setting($data) {
3277         // there miiight be an easier way to do this :)
3278         // if this is changed, make sure the $defaults array above is modified so that this
3279         // function processes it correctly
3281         $keys = array();
3282         $values = array();
3284         foreach ($data as $key => $value) {
3285             if (substr($key,0,1) == 'k') {
3286                 $keys[substr($key,1)] = $value;
3287             } elseif (substr($key,0,1) == 'v') {
3288                 $values[substr($key,1)] = $value;
3289             }
3290         }
3292         $result = array();
3293         for ($i = 0; $i < count($keys); $i++) {
3294             if (($keys[$i] !== '') && ($values[$i] !== '')) {
3295                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).'{:}'.clean_param($values[$i], PARAM_NOTAGS);
3296             }
3297         }
3299         return ($this->config_write($this->name, implode('{;}', $result)) ? '' : get_string('errorsetting', 'admin').$this->visiblename.'<br />');
3300     }
3302     function output_html($data, $query='') {
3303         $fullname = $this->get_full_name();
3304         $return = '<div class="form-group">';
3305         for ($i = 0; $i < count($data) / 2; $i++) {
3306             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
3307             $return .= '&nbsp;&nbsp;';
3308             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
3309         }
3310         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
3311         $return .= '&nbsp;&nbsp;';
3312         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3313         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
3314         $return .= '&nbsp;&nbsp;';
3315         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
3316         $return .= '</div>';
3318         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3319     }
3323 /**
3324  * Setting for spellchecker language selection.
3325  */
3326 class admin_setting_special_editordictionary extends admin_setting_configselect {
3328     function admin_setting_special_editordictionary() {
3329         $name = 'editordictionary';
3330         $visiblename = get_string('editordictionary','admin');
3331         $description = get_string('configeditordictionary', 'admin');
3332         parent::admin_setting_configselect($name, $visiblename, $description, '', NULL);
3333     }
3335     function load_choices() {
3336         // function borrowed from the old moodle/admin/editor.php, slightly modified
3337         // Get all installed dictionaries in the system
3338         if (is_array($this->choices)) {
3339             return true;
3340         }
3342         $this->choices = array();
3344         global $CFG;
3346         clearstatcache();
3348         // If aspellpath isn't set don't even bother ;-)
3349         if (empty($CFG->aspellpath)) {
3350             $this->choices['error'] = 'Empty aspell path!';
3351             return true;
3352         }
3354         // Do we have access to popen function?
3355         if (!function_exists('popen')) {
3356             $this->choices['error'] = 'Popen function disabled!';
3357             return true;
3358         }
3360         $cmd          = $CFG->aspellpath;
3361         $output       = '';
3362         $dictionaries = array();
3364         if(!($handle = @popen(escapeshellarg($cmd).' dump dicts', 'r'))) {
3365             $this->choices['error'] = 'Couldn\'t create handle!';
3366         }
3368         while(!feof($handle)) {
3369             $output .= fread($handle, 1024);
3370         }
3371         @pclose($handle);
3373         $dictionaries = explode(chr(10), $output);
3374         foreach ($dictionaries as $dict) {
3375             if (empty($dict)) {
3376                 continue;
3377             }
3378             $this->choices[$dict] = $dict;
3379         }
3381         if (empty($this->choices)) {
3382             $this->choices['error'] = 'Error! Check your aspell installation!';
3383         }
3384         return true;
3385     }
3389 class admin_setting_special_editorhidebuttons extends admin_setting {
3390     var $items;
3392     function admin_setting_special_editorhidebuttons() {
3393         parent::admin_setting('editorhidebuttons', get_string('editorhidebuttons', 'admin'),
3394                               get_string('confeditorhidebuttons', 'admin'), array());
3395         // weird array... buttonname => buttonimage (assume proper path appended). if you leave buttomimage blank, text will be printed instead
3396         $this->items = array('fontname' => '',
3397                          'fontsize' => '',
3398                          'formatblock' => '',
3399                          'bold' => 'ed_format_bold.gif',
3400                          'italic' => 'ed_format_italic.gif',
3401                          'underline' => 'ed_format_underline.gif',
3402                          'strikethrough' => 'ed_format_strike.gif',
3403                          'subscript' => 'ed_format_sub.gif',
3404                          'superscript' => 'ed_format_sup.gif',
3405                          'copy' => 'ed_copy.gif',
3406                          'cut' => 'ed_cut.gif',
3407                          'paste' => 'ed_paste.gif',
3408                          'clean' => 'ed_wordclean.gif',
3409                          'undo' => 'ed_undo.gif',
3410                          'redo' => 'ed_redo.gif',
3411                          'justifyleft' => 'ed_align_left.gif',
3412                          'justifycenter' => 'ed_align_center.gif',
3413                          'justifyright' => 'ed_align_right.gif',
3414                          'justifyfull' => 'ed_align_justify.gif',
3415                          'lefttoright' => 'ed_left_to_right.gif',
3416                          'righttoleft' => 'ed_right_to_left.gif',
3417                          'insertorderedlist' => 'ed_list_num.gif',
3418                          'insertunorderedlist' => 'ed_list_bullet.gif',
3419                          'outdent' => 'ed_indent_less.gif',
3420                          'indent' => 'ed_indent_more.gif',
3421                          'forecolor' => 'ed_color_fg.gif',
3422                          'hilitecolor' => 'ed_color_bg.gif',
3423                          'inserthorizontalrule' => 'ed_hr.gif',
3424                          'createanchor' => 'ed_anchor.gif',
3425                          'createlink' => 'ed_link.gif',
3426                          'unlink' => 'ed_unlink.gif',
3427                          'insertimage' => 'ed_image.gif',
3428                          'inserttable' => 'insert_table.gif',
3429                          'insertsmile' => 'em.icon.smile.gif',
3430                          'insertchar' => 'icon_ins_char.gif',
3431                          'spellcheck' => 'spell-check.gif',
3432                          'htmlmode' => 'ed_html.gif',
3433                          'popupeditor' => 'fullscreen_maximize.gif',
3434                          'search_replace' => 'ed_replace.gif');
3435     }
3437     function get_setting() {
3438         $result = $this->config_read($this->name);
3439         if (is_null($result)) {
3440             return NULL;
3441         }
3442         if ($result === '') {
3443             return array();
3444         }
3445         return explode(' ', $result);
3446     }
3448     function write_setting($data) {
3449         if (!is_array($data)) {
3450             return ''; // ignore it
3451         }
3452         unset($data['xxxxx']);
3453         $result = array();
3455         foreach ($data as $key => $value) {
3456             if (!isset($this->items[$key])) {
3457                 return get_string('errorsetting', 'admin');
3458             }
3459             if ($value == '1') {
3460                 $result[] = $key;
3461             }
3462         }
3463         return ($this->config_write($this->name, implode(' ', $result)) ? '' : get_string('errorsetting', 'admin'));
3464     }
3466     function output_html($data, $query='') {
3468         global $CFG;
3470         // checkboxes with input name="$this->name[$key]" value="1"
3471         // we do 15 fields per column
3473         $return = '<div class="form-group">';
3474         $return .= '<table><tr><td valign="top" align="right">';
3475         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3477         $count = 0;
3479         foreach($this->items as $key => $value) {
3480             if ($count % 15 == 0 and $count != 0) {
3481                 $return .= '</td><td valign="top" align="right">';
3482             }
3484             $return .= '<label for="'.$this->get_id().$key.'">';
3485             $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;';
3486             $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;';
3487             $return .= '</label>';
3488             $count++;
3489             if ($count % 15 != 0) {
3490                 $return .= '<br /><br />';
3491             }
3492         }
3494         $return .= '</td></tr>';
3495         $return .= '</table>';
3496         $return .= '</div>';
3498         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3499     }
3502 /**
3503  * Special setting for limiting of the list of available languages.
3504  */
3505 class admin_setting_langlist extends admin_setting_configtext {
3506     function admin_setting_langlist() {
3507         parent::admin_setting_configtext('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
3508     }
3510     function write_setting($data) {
3511         $return = parent::write_setting($data);
3512         get_list_of_languages(true);//refresh the list
3513         return $return;
3514     }
3517 /**
3518  * Course category selection
3519  */
3520 class admin_settings_coursecat_select extends admin_setting_configselect {
3521     function admin_settings_coursecat_select($name, $visiblename, $description, $defaultsetting) {
3522         parent::admin_setting_configselect($name, $visiblename, $description, $defaultsetting, NULL);
3523     }
3525     function load_choices() {
3526         global $CFG;
3527         require_once($CFG->dirroot.'/course/lib.php');
3528         if (is_array($this->choices)) {
3529             return true;
3530         }
3531         $this->choices = make_categories_options();
3532         return true;
3533     }
3536 class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
3537     function admin_setting_special_backupdays() {
3538         parent::admin_setting_configmulticheckbox2('backup_sche_weekdays', get_string('schedule'), get_string('backupschedulehelp'), array(), NULL);
3539         $this->plugin = 'backup';
3540     }
3542     function load_choices() {
3543         if (is_array($this->choices)) {
3544             return true;
3545         }
3546         $this->choices = array();
3547         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
3548         foreach ($days as $day) {
3549             $this->choices[$day] = get_string($day, 'calendar');
3550         }
3551         return true;
3552     }
3555 /**
3556  * Special debug setting
3557  */
3558 class admin_setting_special_debug extends admin_setting_configselect {
3559     function admin_setting_special_debug() {
3560         parent::admin_setting_configselect('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
3561     }
3563     function load_choices() {
3564         if (is_array($this->choices)) {
3565             return true;
3566         }
3567         $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
3568                                DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
3569                                DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
3570                                DEBUG_ALL       => get_string('debugall', 'admin'),
3571                                DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
3572         return true;
3573     }
3577 class admin_setting_special_calendar_weekend extends admin_setting {
3578     function admin_setting_special_calendar_weekend() {
3579         $name = 'calendar_weekend';
3580         $visiblename = get_string('calendar_weekend', 'admin');
3581         $description = get_string('helpweekenddays', 'admin');
3582         $default = array ('0', '6'); // Saturdays and Sundays
3583         parent::admin_setting($name, $visiblename, $description, $default);
3584     }
3586     function get_setting() {
3587         $result = $this->config_read($this->name);
3588         if (is_null($result)) {