8ac5a2397437b4e139ba464f809af48fc190a5ab
[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 libraries
13 require_once($CFG->libdir.'/ddllib.php');
14 require_once($CFG->libdir.'/xmlize.php');
15 require_once($CFG->libdir.'/messagelib.php');      // Messagelib functions
17 global $upgradeloghandle, $upgradelogbuffer;
19 $upgradeloghandle = false;
20 $upgradelogbuffer = '';
22 define('INSECURE_DATAROOT_WARNING', 1);
23 define('INSECURE_DATAROOT_ERROR', 2);
25 /**
26  * Central function for upgrading the DB
27  *
28  * @param string $version
29  * @param string $release
30  * @param bool   $unittest If true, bypasses a bunch of confirmation screens
31  */
32 function upgrade_db($version, $release) {
33     global $CFG, $DB, $SESSION, $unittest;
35     if (empty($unittest)) {
36         $unittest = false;
37     }
39     $confirmupgrade          = optional_param('confirmupgrade', $unittest, PARAM_BOOL);
40     $confirmrelease          = optional_param('confirmrelease', $unittest, PARAM_BOOL);
41     $confirmplugins          = optional_param('confirmplugincheck', $unittest, PARAM_BOOL);
42     $agreelicense            = optional_param('agreelicense', $unittest, PARAM_BOOL);
43     $autopilot               = optional_param('autopilot', $unittest, PARAM_BOOL);
44     $setuptesttables         = optional_param('setuptesttables', false, PARAM_BOOL);
45     $continuesetuptesttables = optional_param('continuesetuptesttables', false, PARAM_BOOL);
46     $upgradetesttables       = optional_param('upgradetesttables', false, PARAM_BOOL);
48     $return_url = "$CFG->wwwroot/$CFG->admin/index.php";
49     if ($unittest) {
50         $return_url = "$CFG->wwwroot/$CFG->admin/report/unittest/index.php?continuesetuptesttables=$continuesetuptesttables&amp;upgradetesttables=$upgradetesttables";
51     }
53     /// set install/upgrade autocontinue session flag
54     if ($autopilot) {
55         $SESSION->installautopilot = $autopilot;
56     }
58     /// Check if the main tables have been installed yet or not.
59     if (!$tables = $DB->get_tables() ) {    // No tables yet at all.
60         $maintables = false;
62     } else {                                 // Check for missing main tables
63         $maintables = true;
64         $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml
65         foreach ($mtables as $mtable) {
66             if (!in_array($mtable, $tables)) {
67                 $maintables = false;
68                 break;
69             }
70         }
71     }
72     unset($mtables);
73     unset($tables);
75     if ($unittest && $autopilot) {
76         echo upgrade_get_javascript();
77     }
79     if (!$maintables) {
80     /// hide errors from headers in case debug enabled in config.php
81         $origdebug = $CFG->debug;
82         $CFG->debug = DEBUG_MINIMAL;
83         error_reporting($CFG->debug);
84         if (empty($agreelicense)) {
85             $strlicense = get_string('license');
86             $navigation = build_navigation(array(array('name'=>$strlicense, 'link'=>null, 'type'=>'misc')));
87             print_header($strlicense, $strlicense, $navigation, "", "", false, "&nbsp;", "&nbsp;");
88             print_heading("<a href=\"http://moodle.org\">Moodle</a> - Modular Object-Oriented Dynamic Learning Environment");
89             print_heading(get_string('copyrightnotice'));
90             print_box(text_to_html(get_string('gpl')), 'copyrightnotice');
91             echo "<br />";
92             notice_yesno(get_string('doyouagree'), "index.php?agreelicense=1",
93                                                    "http://docs.moodle.org/en/License");
94             print_footer('none');
95             exit;
96         }
97         if (empty($confirmrelease)) {
98             $strcurrentrelease = get_string("currentrelease");
99             $navigation = build_navigation(array(array('name'=>$strcurrentrelease, 'link'=>null, 'type'=>'misc')));
100             print_header($strcurrentrelease, $strcurrentrelease, $navigation, "", "", false, "&nbsp;", "&nbsp;");
101             print_heading("Moodle $release");
102             print_box(get_string('releasenoteslink', 'admin', 'http://docs.moodle.org/en/Release_Notes'), 'generalbox boxaligncenter boxwidthwide');
103             echo '<form action="index.php"><div>';
104             echo '<input type="hidden" name="agreelicense" value="1" />';
105             echo '<input type="hidden" name="confirmrelease" value="1" />';
106             echo '</div>';
107             echo '<div class="continuebutton"><input name="autopilot" id="autopilot" type="checkbox" value="1" /><label for="autopilot">'.get_string('unattendedoperation', 'admin').'</label>';
108             echo '<br /><br /><input type="submit" value="'.get_string('continue').'" /></div>';
109             echo '</form>';
110             print_footer('none');
111             die;
112         }
114         $strdatabasesetup    = get_string("databasesetup");
115         $strdatabasesuccess  = get_string("databasesuccess");
116         $navigation = build_navigation(array(array('name'=>$strdatabasesetup, 'link'=>null, 'type'=>'misc')));
118         if (!$unittest) {
119             print_header($strdatabasesetup, $strdatabasesetup, $navigation,
120                         "", upgrade_get_javascript(), false, "&nbsp;", "&nbsp;");
121         }
123     /// return to original debugging level
124         $CFG->debug = $origdebug;
125         error_reporting($CFG->debug);
126         upgrade_log_start();
127         $DB->set_debug(true);
129         if (!$DB->setup_is_unicodedb()) {
130             if (!$DB->change_db_encoding()) {
131                 // If could not convert successfully, throw error, and prevent installation
132                 print_error('unicoderequired', 'admin');
133             }
134         }
136         $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml");
138     /// Continue with the instalation
140         // Install the roles system.
141         moodle_install_roles();
143         // Install core event handlers
144         events_update_definition();
146         // Install core message providers
147         message_update_providers();
148         message_update_providers('message');
150         /// This is used to handle any settings that must exist in $CFG but which do not exist in
151         /// admin_get_root()/$ADMIN as admin_setting objects (there are some exceptions).
152         apply_default_exception_settings(array('auth' => 'email',
153                                                'auth_pop3mailbox' => 'INBOX',
154                                                'enrol' => 'manual',
155                                                'enrol_plugins_enabled' => 'manual',
156                                                'style' => 'default',
157                                                'template' => 'default',
158                                                'theme' => 'standardwhite',
159                                                'filter_multilang_converted' => 1));
161         // store main version
162         if (!set_config('version', $version)) {
163             print_error('cannotupdateversion', 'debug');
164         }
167         // Write default settings unconditionally (i.e. even if a setting is already set, overwrite it)
168         // (this should only have any effect during initial install).
169         admin_apply_default_settings(NULL, true);
171         notify($strdatabasesuccess, 'notifysuccess');
173         /// do not show certificates in log ;-)
174         $DB->set_debug(false);
176         // hack - set up mnet
177         require_once $CFG->dirroot.'/mnet/lib.php';
179         print_continue("index.php?continuesetuptesttables=$setuptesttables&amp;upgradetesttables=$upgradetesttables");
180         print_footer('none');
182         die;
183     }
186 /// Check version of Moodle code on disk compared with database
187 /// and upgrade if possible.
189     $stradministration = get_string('administration');
191     if (empty($CFG->version)) {
192         print_error('missingconfigversion', 'debug');
193     }
195     if ($version > $CFG->version) {  // upgrade
197         require_once($CFG->libdir.'/db/upgrade.php'); // Defines upgrades
198         require_once($CFG->libdir.'/db/upgradelib.php');   // Upgrade-related functions
200         $a->oldversion = "$CFG->release ($CFG->version)";
201         $a->newversion = "$release ($version)";
202         $strdatabasechecking = get_string("databasechecking", "", $a);
204         // hide errors from headers in case debug is enabled
205         $origdebug = $CFG->debug;
206         $CFG->debug = DEBUG_MINIMAL;
207         error_reporting($CFG->debug);
208         $CFG->xmlstrictheaders = false;
210         // logo ut in case we are upgrading from pre 1.9 version in order to prevent
211         // weird session/role problems caused by incorrect data in USER and SESSION
212         if ($CFG->version < 2007101500) {
213             require_logout();
214         }
216         if (empty($confirmupgrade)) {
217             $navigation = build_navigation(array(array('name'=>$strdatabasechecking, 'link'=>null, 'type'=>'misc')));
218             print_header($strdatabasechecking, $stradministration, $navigation,
219                     "", "", false, "&nbsp;", "&nbsp;");
221             notice_yesno(get_string('upgradesure', 'admin', $a->newversion), 'index.php?confirmupgrade=1', 'index.php');
222             print_footer('none');
223             exit;
225         } else if (empty($confirmrelease)){
226             $strcurrentrelease = get_string("currentrelease");
227             $navigation = build_navigation(array(array('name'=>$strcurrentrelease, 'link'=>null, 'type'=>'misc')));
228             print_header($strcurrentrelease, $strcurrentrelease, $navigation, "", "", false, "&nbsp;", "&nbsp;");
229             print_heading("Moodle $release");
230             print_box(get_string('releasenoteslink', 'admin', 'http://docs.moodle.org/en/Release_Notes'));
232             require_once($CFG->libdir.'/environmentlib.php');
233             print_heading(get_string('environment', 'admin'));
234             if (!check_moodle_environment($release, $environment_results, true)) {
235                 if (empty($CFG->skiplangupgrade)) {
236                     print_box_start('generalbox', 'notice'); // MDL-8330
237                     print_string('langpackwillbeupdated', 'admin');
238                     print_box_end();
239                 }
240                 notice_yesno(get_string('environmenterrorupgrade', 'admin'),
241                              'index.php?confirmupgrade=1&confirmrelease=1', 'index.php');
242             } else {
243                 notify(get_string('environmentok', 'admin'), 'notifysuccess');
244                 if (empty($CFG->skiplangupgrade)) {
245                     print_box_start('generalbox', 'notice'); // MDL-8330
246                     print_string('langpackwillbeupdated', 'admin');
247                     print_box_end();
248                 }
249                 echo '<form action="index.php"><div>';
250                 echo '<input type="hidden" name="confirmupgrade" value="1" />';
251                 echo '<input type="hidden" name="confirmrelease" value="1" />';
252                 echo '</div>';
253                 echo '<div class="continuebutton">';
254                 echo '<br /><br /><input type="submit" value="'.get_string('continue').'" /></div>';
255                 echo '</form>';
256             }
258             print_footer('none');
259             die;
260         } elseif (empty($confirmplugins)) {
261             $strplugincheck = get_string('plugincheck');
262             $navigation = build_navigation(array(array('name'=>$strplugincheck, 'link'=>null, 'type'=>'misc')));
263             print_header($strplugincheck, $strplugincheck, $navigation, "", "", false, "&nbsp;", "&nbsp;");
264             print_heading($strplugincheck);
265             print_box_start('generalbox', 'notice'); // MDL-8330
266             print_string('pluginchecknotice');
267             print_box_end();
268             print_plugin_tables();
269             echo "<br />";
270             echo '<div class="continuebutton">';
271             print_single_button('index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1), get_string('reload'), 'get');
272             echo '</div><br />';
273             echo '<form action="index.php"><div>';
274             echo '<input type="hidden" name="confirmupgrade" value="1" />';
275             echo '<input type="hidden" name="confirmrelease" value="1" />';
276             echo '<input type="hidden" name="confirmplugincheck" value="1" />';
277             echo '</div>';
278             echo '<div class="continuebutton"><input name="autopilot" id="autopilot" type="checkbox" value="1" /><label for="autopilot">'.get_string('unattendedoperation', 'admin').'</label>';
279             echo '<br /><br /><input type="submit" value="'.get_string('continue').'" /></div>';
280             echo '</form>';
281             print_footer('none');
282             die();
284         } else {
286             $strdatabasesuccess  = get_string("databasesuccess");
287             $navigation = build_navigation(array(array('name'=>$strdatabasesuccess, 'link'=>null, 'type'=>'misc')));
288             print_header($strdatabasechecking, $stradministration, $navigation,
289                     "", upgrade_get_javascript(), false, "&nbsp;", "&nbsp;");
291         /// return to original debugging level
292             $CFG->debug = $origdebug;
293             error_reporting($CFG->debug);
294             upgrade_log_start();
296         /// Upgrade current language pack if we can
297             if (empty($CFG->skiplangupgrade)) {
298                 upgrade_language_pack();
299             }
301             print_heading($strdatabasechecking);
302             $DB->set_debug(true);
303         /// Launch the old main upgrade (if exists)
304             $status = true;
305             if (function_exists('main_upgrade')) {
306                 $status = main_upgrade($CFG->version);
307             }
308         /// If succesful and exists launch the new main upgrade (XMLDB), called xmldb_main_upgrade
309             if ($status && function_exists('xmldb_main_upgrade')) {
310                 $status = xmldb_main_upgrade($CFG->version);
311             }
312             $DB->set_debug(false);
313         /// If successful, continue upgrading roles and setting everything properly
314             if ($status) {
315                 if (!update_capabilities()) {
316                     print_error('cannotupgradecapabilities', 'debug');
317                 }
319                 // Update core events
320                 events_update_definition();
322                 // Update core message providers
323                 message_update_providers();
324                 message_update_providers('message');
326                 if (set_config("version", $version)) {
327                     remove_dir($CFG->dataroot . '/cache', true); // flush cache
328                     notify($strdatabasesuccess, "green");
330                     if ($unittest) {
331                         print_continue("index.php?testtablesok=1");
332                     } else {
333                         print_continue("upgradesettings.php");
334                     }
335                     print_footer('none');
336                     exit;
337                 } else {
338                     print_error('cannotupdateversion', 'debug');
339                 }
340         /// Main upgrade not success
341             } else {
342                 notify('Main Upgrade failed!  See lib/db/upgrade.php');
343                 print_continue('index.php?confirmupgrade=1&amp;confirmrelease=1&amp;confirmplugincheck=1');
344                 print_footer('none');
345                 die;
346             }
347             upgrade_log_finish();
348         }
349     } else if ($version < $CFG->version) {
350         upgrade_log_start();
351         notify("WARNING!!!  The code you are using is OLDER than the version that made these databases!");
352         upgrade_log_finish();
353     }
355 /// Updated human-readable release version if necessary
357     if ($release <> $CFG->release) {  // Update the release version
358         if (!set_config("release", $release)) {
359             print_error("cannotupdaterelease", 'debug');
360         }
361     }
363     // Turn off xmlstrictheaders during upgrade.
364     $origxmlstrictheaders = !empty($CFG->xmlstrictheaders);
365     $CFG->xmlstrictheaders = false;
367 /// Find and check all main modules and load them up or upgrade them if necessary
368 /// first old *.php update and then the new upgrade.php script
369     upgrade_activity_modules($return_url);  // Return here afterwards
371 /// Check all questiontype plugins and upgrade if necessary
372 /// first old *.php update and then the new upgrade.php script
373 /// It is important that this is done AFTER the quiz module has been upgraded
374     upgrade_plugins('qtype', 'question/type', $return_url);  // Return here afterwards
376 /// Upgrade backup/restore system if necessary
377 /// first old *.php update and then the new upgrade.php script
378     require_once("$CFG->dirroot/backup/lib.php");
379     upgrade_backup_db($return_url);  // Return here afterwards
381 /// Upgrade blocks system if necessary
382 /// first old *.php update and then the new upgrade.php script
383     require_once("$CFG->dirroot/lib/blocklib.php");
384     upgrade_blocks_db($return_url);  // Return here afterwards
386 /// Check all blocks and load (or upgrade them if necessary)
387 /// first old *.php update and then the new upgrade.php script
388     upgrade_blocks_plugins($return_url);  // Return here afterwards
390 /// Check all enrolment plugins and upgrade if necessary
391 /// first old *.php update and then the new upgrade.php script
392     upgrade_plugins('enrol', 'enrol', $return_url);  // Return here afterwards
394 /// Check all auth plugins and upgrade if necessary
395     upgrade_plugins('auth','auth',$return_url);
397 /// Check all course formats and upgrade if necessary
398     upgrade_plugins('format','course/format',$return_url);
400 /// Check for local database customisations
401 /// first old *.php update and then the new upgrade.php script
402     require_once("$CFG->dirroot/lib/locallib.php");
403     upgrade_local_db($return_url);  // Return here afterwards
405 /// Check for changes to RPC functions
406     require_once("$CFG->dirroot/$CFG->admin/mnet/adminlib.php");
407     upgrade_RPC_functions($return_url);  // Return here afterwards
409 /// Upgrade all plugins for gradebook
410     upgrade_plugins('gradeexport', 'grade/export', $return_url);
411     upgrade_plugins('gradeimport', 'grade/import', $return_url);
412     upgrade_plugins('gradereport', 'grade/report', $return_url);
414 /// Check all message output plugins and upgrade if necessary
415     upgrade_plugins('message','message/output',$return_url);
417 /// Check all course report plugins and upgrade if necessary
418     upgrade_plugins('coursereport', 'course/report', $return_url);
420 /// Check all admin report plugins and upgrade if necessary
421     upgrade_plugins('report', $CFG->admin.'/report', $return_url);
423 /// Check all quiz report plugins and upgrade if necessary
424     upgrade_plugins('quizreport', 'mod/quiz/report', $return_url);
426 /// Check all portfolio plugins and upgrade if necessary
427     upgrade_plugins('portfolio', 'portfolio/type', $return_url);
429 /// Check all progress tracker plugins and upgrade if necessary
430     upgrade_plugins('trackerexport', 'tracker/export', $return_url);
431     upgrade_plugins('trackerimport', 'tracker/import', $return_url);
432     upgrade_plugins('trackerreport', 'tracker/report', $return_url);
434 /// just make sure upgrade logging is properly terminated
435     upgrade_log_finish();
437     unset($SESSION->installautopilot);
439     // Turn xmlstrictheaders back on now.
440     $CFG->xmlstrictheaders = $origxmlstrictheaders;
442     if (!$unittest) {
443     /// Set up the blank site - to be customized later at the end of install.
444         if (! $site = get_site()) {
445             build_site_course();
446             redirect("index.php?continuesetuptesttables=$continuesetuptesttables&amp;upgradetesttables=$upgradetesttables");
447         }
449         // initialise default blocks on admin and site page if needed
450         if (empty($CFG->adminblocks_initialised)) {
451             require_once("$CFG->dirroot/$CFG->admin/pagelib.php");
452             require_once($CFG->libdir.'/blocklib.php');
453             page_map_class(PAGE_ADMIN, 'page_admin');
454             $page = page_create_object(PAGE_ADMIN, 0); // there must be some id number
455             blocks_repopulate_page($page);
457             //add admin_tree block to site if not already present
458             if ($admintree = $DB->get_record('block', array('name'=>'admin_tree'))) {
459                 $page = page_create_object(PAGE_COURSE_VIEW, SITEID);
460                 $pageblocks=blocks_get_by_page($page);
461                 blocks_execute_action($page, $pageblocks, 'add', (int)$admintree->id, false, false);
462                 if ($admintreeinstance = $DB->get_record('block_instance', array('pagetype'=>$page->type, 'pageid'=>SITEID, 'blockid'=>$admintree->id))) {
463                     $pageblocks=blocks_get_by_page($page);
464                     blocks_execute_action($page, $pageblocks, 'moveleft', $admintreeinstance, false, false);
465                 }
466             }
468             set_config('adminblocks_initialised', 1);
469         }
471     /// Define the unique site ID code if it isn't already
472         if (empty($CFG->siteidentifier)) {    // Unique site identification code
473             set_config('siteidentifier', random_string(32).$_SERVER['HTTP_HOST']);
474         }
476     /// ugly hack - if mnet is not initialised include the mnet lib, it adds needed mnet records and configures config options
477     ///             we should not do such crazy stuff in lib functions!!!
478         if (empty($CFG->mnet_localhost_id)) {
479             require_once $CFG->dirroot.'/mnet/lib.php';
480         }
482     /// Check if the guest user exists.  If not, create one.
483         if (!$DB->record_exists('user', array('username'=>'guest'))) {
484             if (! $guest = create_guest_record()) {
485                 notify("Could not create guest user record !!!");
486             }
487         }
489     /// Set up the admin user
490         if (empty($CFG->rolesactive)) {
491             build_context_path(); // just in case - should not be needed
492             create_admin_user();
493         }
494     } else {
495         build_site_course();
496         create_guest_record();
497         create_admin_user();
498         redirect($return_url);
499     }
502 function build_site_course() {
503     global $CFG, $DB, $unittest;
505     $continuesetuptesttables= optional_param('continuesetuptesttables', $unittest, PARAM_BOOL);
507     // We are about to create the site "course"
508     require_once($CFG->libdir.'/blocklib.php');
510     $newsite = new object();
511     $newsite->fullname = "";
512     $newsite->shortname = "";
513     $newsite->summary = NULL;
514     $newsite->newsitems = 3;
515     $newsite->numsections = 0;
516     $newsite->category = 0;
517     $newsite->format = 'site';  // Only for this course
518     $newsite->teacher = get_string("defaultcourseteacher");
519     $newsite->teachers = get_string("defaultcourseteachers");
520     $newsite->student = get_string("defaultcoursestudent");
521     $newsite->students = get_string("defaultcoursestudents");
522     $newsite->timemodified = time();
524     if (!$newid = $DB->insert_record('course', $newsite)) {
525         print_error('cannotsetupsite', 'error');
526     }
527     // make sure course context exists
528     get_context_instance(CONTEXT_COURSE, $newid);
530     // Site created, add blocks for it
531     $page = page_create_object(PAGE_COURSE_VIEW, $newid);
532     blocks_repopulate_page($page); // Return value not checked because you can always edit later
534     // create default course category
535     $cat = get_course_category();
539 /**
540  * Upgrade savepoint, marks end of each upgrade block.
541  * It stores new main version, resets upgrade timeout
542  * and abort upgrade if user cancels page loading.
543  *
544  * Please do not make large upgrade blocks with lots of operations,
545  * for example when adding tables keep only one table operation per block.
546  *
547  * @param bool $result false if upgrade step failed, true if completed
548  * @param string or float $version main version
549  * @return void
550  */
551 function upgrade_main_savepoint($result, $version) {
552     global $CFG;
554     if ($result) {
555         if ($CFG->version >= $version) {
556             // something really wrong is going on in main upgrade script
557             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$CFG->version, 'newversion'=>$version));
558         }
559         set_config('version', $version);
560     } else {
561         notify ("Upgrade savepoint: Error during main upgrade to version $version");
562     }
564     // reset upgrade timeout to default
565     upgrade_set_timeout();
567     // this is a safe place to stop upgrades if user aborts page loading
568     if (connection_aborted()) {
569         die;
570     }
573 /**
574  * Module upgrade savepoint, marks end of module upgrade blocks
575  * It stores module version, resets upgrade timeout
576  * and abort upgrade if usercancels page loading.
577  *
578  * @param bool $result false if upgrade step failed, true if completed
579  * @param string or float $version main version
580  * @return void
581  */
582 function upgrade_mod_savepoint($result, $version, $modname) {
583     global $DB;
585     if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
586         print_error('modulenotexist', 'debug', '', $modname);
587     }
589     if ($result) {
590         if ($module->version >= $version) {
591             // something really wrong is going on in upgrade script
592             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$module->version, 'newversion'=>$version));
593         }
594         $module->version = $version;
595         $DB->update_record('modules', $module);
596     } else {
597         notify ("Upgrade savepoint: Error during mod upgrade to version $version");
598     }
600     // reset upgrade timeout to default
601     upgrade_set_timeout();
603     // this is a safe place to stop upgrades if user aborts page loading
604     if (connection_aborted()) {
605         die;
606     }
609 function upgrade_blocks_savepoint($result, $version, $blockname) {
610     global $DB;
612     if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
613         print_error('blocknotexist', 'debug', '', $blockname);
614     }
616     if ($result) {
617         if ($block->version >= $version) {
618             // something really wrong is going on in upgrade script
619             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$block->version, 'newversion'=>$version));
620         }
621         $block->version = $version;
622         $DB->update_record('block', $block);
623     } else {
624         notify ("Upgrade savepoint: Error during mod upgrade to version $version");
625     }
627     // reset upgrade timeout to default
628     upgrade_set_timeout();
630     // this is a safe place to stop upgrades if user aborts page loading
631     if (connection_aborted()) {
632         die;
633     }
636 function upgrade_plugin_savepoint($result, $version, $type, $dir) {
637     if ($result) {
638         $fullname = $type . '_' . $dir;
639         $installedversion = get_config($fullname, 'version');
640         if ($installedversion >= $version) {
641             // Something really wrong is going on in the upgrade script
642             $a = new stdClass;
643             $a->oldversion = $installedversion;
644             $a->newversion = $version;
645             print_error('cannotdowngrade', 'debug', '', $a);
646         }
647         set_config('version', $version, $fullname);
648     } else {
649         notify ("Upgrade savepoint: Error during mod upgrade to version $version");
650     }
652     // Reset upgrade timeout to default
653     upgrade_set_timeout();
655     // This is a safe place to stop upgrades if user aborts page loading
656     if (connection_aborted()) {
657         die;
658     }
661 function upgrade_backup_savepoint($result, $version) {
662     //TODO
665 /**
666  * Delete all plugin tables
667  * @name string name of plugin, used as table prefix
668  * @file string path to install.xml file
669  * @feedback boolean
670  */
671 function drop_plugin_tables($name, $file, $feedback=true) {
672     global $CFG, $DB;
674     // first try normal delete
675     if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
676         return true;
677     }
679     // then try to find all tables that start with name and are not in any xml file
680     $used_tables = get_used_table_names();
682     $tables = $DB->get_tables();
684     /// Iterate over, fixing id fields as necessary
685     foreach ($tables as $table) {
686         if (in_array($table, $used_tables)) {
687             continue;
688         }
690         if (strpos($table, $name) !== 0) {
691             continue;
692         }
694         // found orphan table --> delete it
695         if ($DB->get_manager()->table_exists($table)) {
696             $xmldb_table = new xmldb_table($table);
697             $DB->get_manager()->drop_table($xmldb_table);
698         }
699     }
701     return true;
704 /**
705  * Returns names of all known tables == tables that moodle knowns about.
706  * @return array of lowercase table names
707  */
708 function get_used_table_names() {
709     $table_names = array();
710     $dbdirs = get_db_directories();
712     foreach ($dbdirs as $dbdir) {
713         $file = $dbdir.'/install.xml';
715         $xmldb_file = new xmldb_file($file);
717         if (!$xmldb_file->fileExists()) {
718             continue;
719         }
721         $loaded    = $xmldb_file->loadXMLStructure();
722         $structure =& $xmldb_file->getStructure();
724         if ($loaded and $tables = $structure->getTables()) {
725             foreach($tables as $table) {
726                 $table_names[] = strtolower($table->name);
727             }
728         }
729     }
731     return $table_names;
734 /**
735  * Returns list of all directories where we expect install.xml files
736  * @return array of paths
737  */
738 function get_db_directories() {
739     global $CFG;
741     $dbdirs = array();
743 /// First, the main one (lib/db)
744     $dbdirs[] = $CFG->libdir.'/db';
746 /// Now, activity modules (mod/xxx/db)
747     if ($plugins = get_list_of_plugins('mod')) {
748         foreach ($plugins as $plugin) {
749             $dbdirs[] = $CFG->dirroot.'/mod/'.$plugin.'/db';
750         }
751     }
753 /// Now, assignment submodules (mod/assignment/type/xxx/db)
754     if ($plugins = get_list_of_plugins('mod/assignment/type')) {
755         foreach ($plugins as $plugin) {
756             $dbdirs[] = $CFG->dirroot.'/mod/assignment/type/'.$plugin.'/db';
757         }
758     }
760 /// Now, question types (question/type/xxx/db)
761     if ($plugins = get_list_of_plugins('question/type')) {
762         foreach ($plugins as $plugin) {
763             $dbdirs[] = $CFG->dirroot.'/question/type/'.$plugin.'/db';
764         }
765     }
767 /// Now, backup/restore stuff (backup/db)
768     $dbdirs[] = $CFG->dirroot.'/backup/db';
770 /// Now, block system stuff (blocks/db)
771     $dbdirs[] = $CFG->dirroot.'/blocks/db';
773 /// Now, blocks (blocks/xxx/db)
774     if ($plugins = get_list_of_plugins('blocks', 'db')) {
775         foreach ($plugins as $plugin) {
776             $dbdirs[] = $CFG->dirroot.'/blocks/'.$plugin.'/db';
777         }
778     }
780 /// Now, course formats (course/format/xxx/db)
781     if ($plugins = get_list_of_plugins('course/format', 'db')) {
782         foreach ($plugins as $plugin) {
783             $dbdirs[] = $CFG->dirroot.'/course/format/'.$plugin.'/db';
784         }
785     }
787 /// Now, enrolment plugins (enrol/xxx/db)
788     if ($plugins = get_list_of_plugins('enrol', 'db')) {
789         foreach ($plugins as $plugin) {
790             $dbdirs[] = $CFG->dirroot.'/enrol/'.$plugin.'/db';
791         }
792     }
794 /// Now admin report plugins (admin/report/xxx/db)
795     if ($plugins = get_list_of_plugins($CFG->admin.'/report', 'db')) {
796         foreach ($plugins as $plugin) {
797             $dbdirs[] = $CFG->dirroot.'/'.$CFG->admin.'/report/'.$plugin.'/db';
798         }
799     }
801 /// Now quiz report plugins (mod/quiz/report/xxx/db)
802     if ($plugins = get_list_of_plugins('mod/quiz/report', 'db')) {
803         foreach ($plugins as $plugin) {
804             $dbdirs[] = $CFG->dirroot.'/mod/quiz/report/'.$plugin.'/db';
805         }
806     }
808     if ($plugins = get_list_of_plugins('portfolio/type', 'db')) {
809         foreach ($plugins as $plugin) {
810             $dbdirs[] = $CFG->dirroot . '/portfolio/type/' . $plugin . '/db';
811         }
812     }
814 /// Local database changes, if the local folder exists.
815     if (file_exists($CFG->dirroot . '/local')) {
816         $dbdirs[] = $CFG->dirroot.'/local/db';
817     }
819     return $dbdirs;
822 /**
823  * Upgrade plugins
824  *
825  * @uses $CFG
826  * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
827  * @param string $dir  The directory where the plugins are located (e.g. 'question/questiontypes')
828  * @param string $return The url to prompt the user to continue to
829  */
830 function upgrade_plugins($type, $dir, $return) {
831     global $CFG, $interactive, $DB;
833 /// Let's know if the header has been printed, so the funcion is being called embedded in an outer page
834     $embedded = defined('HEADER_PRINTED');
836     $plugs = get_list_of_plugins($dir);
837     $updated_plugins = false;
838     $strpluginsetup  = get_string('pluginsetup');
840     foreach ($plugs as $plug) {
842         $fullplug = $CFG->dirroot .'/'.$dir.'/'. $plug;
844         unset($plugin);
846         if (is_readable($fullplug .'/version.php')) {
847             include_once($fullplug .'/version.php');  // defines $plugin with version etc
848         } else {
849             continue;                              // Nothing to do.
850         }
852         $newupgrade = false;
853         if (is_readable($fullplug . '/db/upgrade.php')) {
854             include_once($fullplug . '/db/upgrade.php');  // defines new upgrading function
855             $newupgrade = true;
856         }
858         if (!isset($plugin)) {
859             continue;
860         }
862         if (!empty($plugin->requires)) {
863             if ($plugin->requires > $CFG->version) {
864                 $info = new object();
865                 $info->pluginname = $plug;
866                 $info->pluginversion  = $plugin->version;
867                 $info->currentmoodle = $CFG->version;
868                 $info->requiremoodle = $plugin->requires;
869                 if (!$updated_plugins && !$embedded) {
870                     print_header($strpluginsetup, $strpluginsetup,
871                         build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
872                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
873                 }
874                 upgrade_log_start();
875                 notify(get_string('pluginrequirementsnotmet', 'error', $info));
876                 $updated_plugins = true;
877                 continue;
878             }
879         }
881         $plugin->name = $plug;   // The name MUST match the directory
882         $plugin->fullname = $type.'_'.$plug;   // The name MUST match the directory
884         $installedversion = get_config($plugin->fullname, 'version');
886         if ($installedversion === false) {
887             set_config('version', 0, $plugin->fullname);
888         }
890         if ($installedversion == $plugin->version) {
891             // do nothing
892         } else if ($installedversion < $plugin->version) {
893             if (!$updated_plugins && !$embedded) {
894                 print_header($strpluginsetup, $strpluginsetup,
895                         build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
896                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
897             }
898             $updated_plugins = true;
899             upgrade_log_start();
900             print_heading($dir.'/'. $plugin->name .' plugin needs upgrading');
901             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
902                 $DB->set_debug(true);
903             }
904             @set_time_limit(0);  // To allow slow databases to complete the long SQL
906             if ($installedversion == 0) {    // It's a new install of this plugin
907             /// Both old .sql files and new install.xml are supported
908             /// but we priorize install.xml (XMLDB) if present
909                 if (file_exists($fullplug . '/db/install.xml')) {
910                     $DB->get_manager()->install_from_xmldb_file($fullplug . '/db/install.xml'); //New method
911                 }
912                 $status = true;
913                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
914                     $DB->set_debug(false);
915                 }
916             /// Continue with the instalation, roles and other stuff
917                 if ($status) {
918                 /// OK so far, now update the plugins record
919                     set_config('version', $plugin->version, $plugin->fullname);
921                 /// Install capabilities
922                     if (!update_capabilities($type.'/'.$plug)) {
923                         print_error('cannotsetupcapforplugin', '', '', $plugin->name);
924                     }
925                 /// Install events
926                     events_update_definition($type.'/'.$plug);
928                 /// Install message providers
929                     message_update_providers($type.'/'.$plug);
931                 /// Run local install function if there is one
932                     if (is_readable($fullplug . '/lib.php')) {
933                         include_once($fullplug . '/lib.php');
934                         $installfunction = $plugin->name.'_install';
935                         if (function_exists($installfunction)) {
936                             if (! $installfunction() ) {
937                                 notify('Encountered a problem running install function for '.$module->name.'!');
938                             }
939                         }
940                     }
942                     notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
943                 } else {
944                     notify('Installing '. $plugin->name .' FAILED!');
945                 }
946             } else {                            // Upgrade existing install
947             /// Run the upgrade function for the plugin.
948                 $newupgrade_function = 'xmldb_' .$plugin->fullname .'_upgrade';
949                 $newupgrade_status = true;
950                 if ($newupgrade && function_exists($newupgrade_function)) {
951                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
952                         $DB->set_debug(true);
953                     }
954                     $newupgrade_status = $newupgrade_function($installedversion);
955                 } else if ($newupgrade) {
956                     notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
957                              $fullplug . '/db/upgrade.php');
958                 }
959                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
960                     $DB->set_debug(false);
961                 }
962             /// Now analyze upgrade results
963                 if ($newupgrade_status) {    // No upgrading failed
964                 /// OK so far, now update the plugins record
965                     set_config('version', $plugin->version, $plugin->fullname);
966                     if (!update_capabilities($type.'/'.$plug)) {
967                         print_error('cannotupdateplugincap', '', '', $plugin->name);
968                     }
969                 /// Update events
970                     events_update_definition($type.'/'.$plug);
972                 /// Update message providers
973                     message_update_providers($type.'/'.$plug);
975                     notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
976                 } else {
977                     notify('Upgrading '. $plugin->name .' from '. $installedversion .' to '. $plugin->version .' FAILED!');
978                 }
979             }
980             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
981                 echo '<hr />';
982             }
983         } else {
984             upgrade_log_start();
985             print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$installedversion, 'newversion'=>$plugin->version));
986         }
987     }
989     upgrade_log_finish();
991     if ($updated_plugins && !$embedded) {
992         if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
993             print_continue($return);
994             print_footer('none');
995             die;
996         } else if (CLI_UPGRADE && ($interactive > CLI_SEMI )) {
997             console_write(STDOUT,'askcontinue');
998             if (read_boolean()){
999                 return ;
1000             } else {
1001                 console_write(STDERR,'','',false);
1002             }
1003         }
1004     }
1007 /**
1008  * Find and check all modules and load them up or upgrade them if necessary
1009  *
1010  * @uses $CFG
1011  * @param string $return The url to prompt the user to continue to
1012  * @todo Finish documenting this function
1013  */
1014 function upgrade_activity_modules($return) {
1016     global $CFG, $interactive, $DB, $unittest;
1018     if (!$mods = get_list_of_plugins('mod') ) {
1019         print_error('nomodules', 'debug');
1020     }
1022     $updated_modules = false;
1023     $strmodulesetup  = get_string('modulesetup');
1025     foreach ($mods as $mod) {
1027         if ($mod == 'NEWMODULE') {   // Someone has unzipped the template, ignore it
1028             continue;
1029         }
1031         $fullmod = $CFG->dirroot .'/mod/'. $mod;
1033         unset($module);
1035         if ( is_readable($fullmod .'/version.php')) {
1036             include_once($fullmod .'/version.php');  // defines $module with version etc
1037         } else {
1038             notify('Module '. $mod .': '. $fullmod .'/version.php was not readable');
1039             continue;
1040         }
1042         $newupgrade = false;
1043         if ( is_readable($fullmod . '/db/upgrade.php')) {
1044             include_once($fullmod . '/db/upgrade.php');  // defines new upgrading function
1045             $newupgrade = true;
1046         }
1048         if (!isset($module)) {
1049             continue;
1050         }
1052         if (!empty($module->requires)) {
1053             if ($module->requires > $CFG->version) {
1054                 $info = new object();
1055                 $info->modulename = $mod;
1056                 $info->moduleversion  = $module->version;
1057                 $info->currentmoodle = $CFG->version;
1058                 $info->requiremoodle = $module->requires;
1059                 if (!$updated_modules) {
1060                     print_header($strmodulesetup, $strmodulesetup,
1061                             build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
1062                             upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
1063                 }
1064                 upgrade_log_start();
1065                 notify(get_string('modulerequirementsnotmet', 'error', $info));
1066                 $updated_modules = true;
1067                 continue;
1068             }
1069         }
1071         $module->name = $mod;   // The name MUST match the directory
1073         include_once($fullmod.'/lib.php');  // defines upgrading and/or installing functions
1075         if ($currmodule = $DB->get_record('modules', array('name'=>$module->name))) {
1076             if ($currmodule->version == $module->version) {
1077                 // do nothing
1078             } else if ($currmodule->version < $module->version) {
1079             /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
1080                 if (!$newupgrade) {
1081                     notify('Upgrade file ' . $mod . ': ' . $fullmod . '/db/upgrade.php is not readable');
1082                     continue;
1083                 }
1084                 if (!$updated_modules) {
1085                     print_header($strmodulesetup, $strmodulesetup,
1086                             build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
1087                             upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
1088                 }
1089                 upgrade_log_start();
1091                 print_heading($module->name .' module needs upgrading');
1093             /// Run de old and new upgrade functions for the module
1094                 $newupgrade_function = 'xmldb_' . $module->name . '_upgrade';
1096             /// Then, the new function if exists and the old one was ok
1097                 $newupgrade_status = true;
1098                 if ($newupgrade && function_exists($newupgrade_function)) {
1099                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
1100                         $DB->set_debug(true);
1101                     }
1102                     $newupgrade_status = $newupgrade_function($currmodule->version, $module);
1103                 } else if ($newupgrade) {
1104                     notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
1105                              $mod . ': ' . $fullmod . '/db/upgrade.php');
1106                 }
1107                 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
1108                     $DB->set_debug(false);
1109                 }
1110             /// Now analyze upgrade results
1111                 if ($newupgrade_status) {    // No upgrading failed
1112                     // OK so far, now update the modules record
1113                     $module->id = $currmodule->id;
1114                     if (!$DB->update_record('modules', $module)) {
1115                         print_error('cannotupdatemod', '', '', $module->name);
1116                     }
1117                     remove_dir($CFG->dataroot . '/cache', true); // flush cache
1118                     notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
1119                     if (!defined('CLI_UPGRADE') || !CLI_UPGRADE) {
1120                        echo '<hr />';
1121                     }
1122                 } else {
1123                     notify('Upgrading '. $module->name .' from '. $currmodule->version .' to '. $module->version .' FAILED!');
1124                 }
1126             /// Update the capabilities table?
1127                 if (!update_capabilities('mod/'.$module->name)) {
1128                     print_error('cannotupdatemodcap', '', '', $module->name);
1129                 }
1131             /// Update events
1132                 events_update_definition('mod/'.$module->name);
1134             /// Update message providers
1135                 message_update_providers('mod/'.$module->name);
1137                 $updated_modules = true;
1139             } else {
1140                 upgrade_log_start();
1141                 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$currmodule->version, 'newversion'=>$module->version));
1142             }
1144         } else {    // module not installed yet, so install it
1145             if (!$updated_modules) {
1146                 if ((!defined('CLI_UPGRADE') || !CLI_UPGRADE) && !$unittest) {
1147                     print_header($strmodulesetup, $strmodulesetup,
1148                         build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '',
1149                         upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
1150                 }
1151             }
1152             upgrade_log_start();
1153             print_heading($module->name);
1154             $updated_modules = true;
1155             // To avoid unnecessary output from the SQL queries in the CLI version
1156             if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
1157                 $DB->set_debug(true);
1158             }
1159             @set_time_limit(0);  // To allow slow databases to complete the long SQL
1161         /// Both old .sql files and new install.xml are supported
1162         /// but we priorize install.xml (XMLDB) if present
1163             if (file_exists($fullmod . '/db/install.xml')) {
1164                 $DB->get_manager()->install_from_xmldb_file($fullmod . '/db/install.xml'); //New method
1165                 $status = true;
1166             }
1167             if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
1168                 $DB->set_debug(false);
1169             }
1171         /// Continue with the installation, roles and other stuff
1172             if ($status) {
1173                 if ($module->id = $DB->insert_record('modules', $module)) {
1175                 /// Capabilities
1176                     if (!update_capabilities('mod/'.$module->name)) {
1177                         print_error('cannotsetupcapformod', '', '', $module->name);
1178                     }
1180                 /// Events
1181                     events_update_definition('mod/'.$module->name);
1183                 /// Message providers
1184                     message_update_providers('mod/'.$module->name);
1186                 /// Run local install function if there is one
1187                     $installfunction = $module->name.'_install';
1188                     if (function_exists($installfunction)) {
1189                         if (! $installfunction() ) {
1190                             notify('Encountered a problem running install function for '.$module->name.'!');
1191                         }
1192                     }
1194                     notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
1195                     if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
1196                        echo '<hr />';
1197                     }
1198                 } else {
1199                     print_error('cannotaddmodule', '', '', $module->name);
1200                 }
1201             } else {
1202                 print_error('cannotsetuptable', 'debug', '', $module->name);
1203             }
1204         }
1206     /// Check submodules of this module if necessary
1208         $submoduleupgrade = $module->name.'_upgrade_submodules';
1209         if (function_exists($submoduleupgrade)) {
1210             $submoduleupgrade();
1211         }
1213     /// Run any defaults or final code that is necessary for this module
1215         if ( is_readable($fullmod .'/defaults.php')) {
1216             // Insert default values for any important configuration variables
1217             unset($defaults);
1218             include($fullmod .'/defaults.php'); // include here means execute, not library include
1219             if (!empty($defaults)) {
1220                 if (!empty($defaults['_use_config_plugins'])) {
1221                     unset($defaults['_use_config_plugins']);
1222                     $localcfg = get_config($module->name);
1223                     foreach ($defaults as $name => $value) {
1224                         if (!isset($localcfg->$name)) {
1225                             set_config($name, $value, $module->name);
1226                         }
1227                     }
1228                 } else {
1229                     foreach ($defaults as $name => $value) {
1230                         if (!isset($CFG->$name)) {
1231                             set_config($name, $value);
1232                         }
1233                     }
1234                 }
1235             }
1236         }
1237     }
1239     upgrade_log_finish(); // finish logging if started
1241     if ($updated_modules) {
1242         if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
1243             print_continue($return);
1244             print_footer('none');
1245             die;
1246         } else if ( CLI_UPGRADE && ($interactive > CLI_SEMI) ) {
1247             console_write(STDOUT,'askcontinue');
1248             if (read_boolean()){
1249                 return ;
1250             }else {
1251                 console_write(STDERR,'','',false);
1252             }
1253         }
1254     }
1257 /**
1258  * Try to obtain or release the cron lock.
1259  *
1260  * @param string  $name  name of lock
1261  * @param int  $until timestamp when this lock considered stale, null means remove lock unconditionaly
1262  * @param bool $ignorecurrent ignore current lock state, usually entend previous lock
1263  * @return bool true if lock obtained
1264  */
1265 function set_cron_lock($name, $until, $ignorecurrent=false) {
1266     global $DB;
1267     if (empty($name)) {
1268         debugging("Tried to get a cron lock for a null fieldname");
1269         return false;
1270     }
1272     // remove lock by force == remove from config table
1273     if (is_null($until)) {
1274         set_config($name, null);
1275         return true;
1276     }
1278     if (!$ignorecurrent) {
1279         // read value from db - other processes might have changed it
1280         $value = $DB->get_field('config', 'value', array('name'=>$name));
1282         if ($value and $value > time()) {
1283             //lock active
1284             return false;
1285         }
1286     }
1288     set_config($name, $until);
1289     return true;
1292 function print_progress($done, $total, $updatetime=5, $sleeptime=1, $donetext='') {
1293     static $thisbarid;
1294     static $starttime;
1295     static $lasttime;
1297     if ($total < 2) {   // No need to show anything
1298         return;
1299     }
1301     // Are we done?
1302     if ($done >= $total) {
1303         $done = $total;
1304         if (!empty($thisbarid)) {
1305             $donetext .= ' ('.$done.'/'.$total.') '.get_string('success');
1306             print_progress_redraw($thisbarid, $done, $total, 500, $donetext);
1307             $thisbarid = $starttime = $lasttime = NULL;
1308         }
1309         return;
1310     }
1312     if (empty($starttime)) {
1313         $starttime = $lasttime = time();
1314         $lasttime = $starttime - $updatetime;
1315         $thisbarid = uniqid();
1316         echo '<table width="500" cellpadding="0" cellspacing="0" align="center"><tr><td width="500">';
1317         echo '<div id="bar'.$thisbarid.'" style="border-style:solid;border-width:1px;width:500px;height:50px;">';
1318         echo '<div id="slider'.$thisbarid.'" style="border-style:solid;border-width:1px;height:48px;width:10px;background-color:green;"></div>';
1319         echo '</div>';
1320         echo '<div id="text'.$thisbarid.'" align="center" style="width:500px;"></div>';
1321         echo '</td></tr></table>';
1322         echo '</div>';
1323     }
1325     $now = time();
1327     if ($done && (($now - $lasttime) >= $updatetime)) {
1328         $elapsedtime = $now - $starttime;
1329         $projectedtime = (int)(((float)$total / (float)$done) * $elapsedtime) - $elapsedtime;
1330         $percentage = round((float)$done / (float)$total, 2);
1331         $width = (int)(500 * $percentage);
1333         if ($projectedtime > 10) {
1334             $projectedtext = '  Ending: '.format_time($projectedtime);
1335         } else {
1336             $projectedtext = '';
1337         }
1339         $donetext .= ' ('.$done.'/'.$total.') '.$projectedtext;
1340         print_progress_redraw($thisbarid, $done, $total, $width, $donetext);
1342         $lasttime = $now;
1343     }
1346 // Don't call this function directly, it's called from print_progress.
1347 function print_progress_redraw($thisbarid, $done, $total, $width, $donetext='') {
1348     if (empty($thisbarid)) {
1349         return;
1350     }
1351     echo '<script>';
1352     echo 'document.getElementById("text'.$thisbarid.'").innerHTML = "'.addslashes_js($donetext).'";'."\n";
1353     echo 'document.getElementById("slider'.$thisbarid.'").style.width = \''.$width.'px\';'."\n";
1354     echo '</script>';
1357 function upgrade_get_javascript() {
1358     global $CFG, $SESSION;
1360     if (!empty($SESSION->installautopilot)) {
1361         $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = true;</script>'."\n";
1362     } else {
1363         $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = false;</script>'."\n";
1364     }
1365     $linktoscrolltoerrors .= '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>';
1367     return $linktoscrolltoerrors;
1370 function create_admin_user($user_input=NULL) {
1371     global $CFG, $USER, $DB, $unittest;
1373     if (empty($CFG->rolesactive)) {   // No admin user yet.
1375         $user = new object();
1376         $user->auth         = 'manual';
1377         $user->firstname    = get_string('admin');
1378         $user->lastname     = get_string('user');
1379         $user->username     = 'admin';
1380         $user->password     = hash_internal_user_password('admin');
1381         $user->email        = 'root@localhost';
1382         $user->confirmed    = 1;
1383         $user->mnethostid   = $CFG->mnet_localhost_id;
1384         $user->lang         = $CFG->lang;
1385         $user->maildisplay  = 1;
1386         $user->timemodified = time();
1388         if ($user_input) {
1389             $user = $user_input;
1390         }
1391         if (!$user->id = $DB->insert_record('user', $user)) {
1392             print_error('cannotcreateadminuser', 'debug');
1393         }
1395         if (!$user = $DB->get_record('user', array('id'=>$user->id))) {   // Double check.
1396             print_error('invaliduserid');
1397         }
1399         if (!$unittest) {
1400             // Assign the default admin roles to the new user.
1401             if (!$adminroles = get_roles_with_capability('moodle/legacy:admin', CAP_ALLOW)) {
1402                 print_error('noadminrole', 'message');
1403             }
1405             $sitecontext = get_context_instance(CONTEXT_SYSTEM);
1406             foreach ($adminroles as $adminrole) {
1407                 role_assign($adminrole->id, $user->id, 0, $sitecontext->id);
1408             }
1410             //set default message preferences
1411             if (!message_set_default_message_preferences( $user )){
1412                 print_error('cannotsavemessageprefs', 'message');
1413             }
1415             // Log the user in.
1416             set_config('rolesactive', 1);
1417             $USER = get_complete_user_data('username', 'admin');
1418             $USER->newadminuser = 1;
1419             load_all_capabilities();
1421             if (!defined('CLI_UPGRADE')||!CLI_UPGRADE) {
1422               redirect("$CFG->wwwroot/user/editadvanced.php?id=$user->id");  // Edit thyself
1423             }
1424         } else {
1425             redirect("$CFG->wwwroot/$CFG->admin/report/unittest/index.php?testtablesok=1");
1426         }
1427     } else {
1428         print_error('cannotcreateadminuser', 'debug');
1429     }
1432 ////////////////////////////////////////////////
1433 /// upgrade logging functions
1434 ////////////////////////////////////////////////
1436 /**
1437  * Marks start of upgrade, blocks any other access to site.
1438  * The upgrade is finished at the end of script or after timeout.
1439  */
1440 function start_upgrade() {
1441     global $CFG, $DB;
1443     static $started = false;
1445     if ($started) {
1446         upgrade_set_timeout(120);
1448     } else {
1449         ignore_user_abort(true);
1450         register_shutdown_function('upgrade_finished_handler');
1451         if ($CFG->version === '' || !$DB->get_manager()->table_exists(new xmldb_table('config'))) {
1452             // db not installed yet
1453             $CFG->upgraderunning = time()+300;
1454         } else {
1455             set_config('upgraderunning', time()+300);
1456         }
1457         $started = true;
1458     }
1461 /**
1462  * Internal function - executed at the very end of each upgrade.
1463  */
1464 function upgrade_finished_handler() {
1465     upgrade_log_finish();
1466     unset_config('upgraderunning');
1467     ignore_user_abort(false);
1470 /**
1471  * Start logging of output into file (if not disabled) and
1472  * prevent aborting and concurrent execution of upgrade script.
1473  *
1474  * Please note that you can not write into session variables after calling this function!
1475  *
1476  * This function may be called repeatedly.
1477  */
1478 function upgrade_log_start() {
1479     global $upgradeloghandle;
1481     start_upgrade(); // make sure the upgrade is started
1483     if ($upgradeloghandle and ($upgradeloghandle !== 'error')) {
1484         return;
1485     }
1487     make_upload_directory('upgradelogs');
1488     ob_start('upgrade_log_callback', 2); // function for logging to disk; flush each line of text ASAP
1491 /**
1492  * Terminate logging of output, flush all data.
1493  *
1494  * Please make sure that each upgrade_log_start() is properly terminated by
1495  * this function or print_error().
1496  *
1497  * This function may be called repeatedly.
1498  */
1499 function upgrade_log_finish() {
1500     global $CFG, $upgradeloghandle, $upgradelogbuffer;
1502     @ob_end_flush();
1503     if ($upgradelogbuffer !== '') {
1504         @fwrite($upgradeloghandle, $upgradelogbuffer);
1505         $upgradelogbuffer = '';
1506     }
1507     if ($upgradeloghandle and ($upgradeloghandle !== 'error')) {
1508         @fclose($upgradeloghandle);
1509         $upgradeloghandle = false;
1510     }
1513 /**
1514  * Callback function for logging into files. Not more than one file is created per minute,
1515  * upgrade session (terminated by upgrade_log_finish()) is always stored in one file.
1516  *
1517  * This function must not output any characters or throw warnigns and errors!
1518  */
1519 function upgrade_log_callback($string) {
1520     global $CFG, $upgradeloghandle, $upgradelogbuffer;
1522     if (empty($CFG->disableupgradelogging) and ($string != '') and ($upgradeloghandle !== 'error')) {
1523         if ($upgradeloghandle or ($upgradeloghandle = @fopen($CFG->dataroot.'/upgradelogs/upg_'.date('Ymd-Hi').'.html', 'a'))) {
1524             $upgradelogbuffer .= $string;
1525             if (strlen($upgradelogbuffer) > 2048) { // 2kB write buffer
1526                 @fwrite($upgradeloghandle, $upgradelogbuffer);
1527                 $upgradelogbuffer = '';
1528             }
1529         } else {
1530             $upgradeloghandle = 'error';
1531         }
1532     }
1533     return $string;
1536 /**
1537  * Test if and critical warnings are present
1538  * @return bool
1539  */
1540 function admin_critical_warnings_present() {
1541     global $SESSION;
1543     if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
1544         return 0;
1545     }
1547     if (!isset($SESSION->admin_critical_warning)) {
1548         $SESSION->admin_critical_warning = 0;
1549         if (ini_get_bool('register_globals')) {
1550             $SESSION->admin_critical_warning = 1;
1551         } else if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
1552             $SESSION->admin_critical_warning = 1;
1553         }
1554     }
1556     return $SESSION->admin_critical_warning;
1559 /**
1560  * Try to verify that dataroot is not accessible from web.
1561  * It is not 100% correct but might help to reduce number of vulnerable sites.
1562  *
1563  * Protection from httpd.conf and .htaccess is not detected properly.
1564  * @param bool $fetchtest try to test public access by fetching file
1565  * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING migth be problematic
1566  */
1567 function is_dataroot_insecure($fetchtest=false) {
1568     global $CFG;
1570     $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
1572     $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
1573     $rp = strrev(trim($rp, '/'));
1574     $rp = explode('/', $rp);
1575     foreach($rp as $r) {
1576         if (strpos($siteroot, '/'.$r.'/') === 0) {
1577             $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
1578         } else {
1579             break; // probably alias root
1580         }
1581     }
1583     $siteroot = strrev($siteroot);
1584     $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
1586     if (strpos($dataroot, $siteroot) !== 0) {
1587         return false;
1588     }
1590     if (!$fetchtest) {
1591         return INSECURE_DATAROOT_WARNING;
1592     }
1594     // now try all methods to fetch a test file using http protocol
1596     $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
1597     preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
1598     $httpdocroot = $matches[1];
1599     $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
1600     if (make_upload_directory('diag', false) === false) {
1601         return INSECURE_DATAROOT_WARNING;
1602     }
1603     $testfile = $CFG->dataroot.'/diag/public.txt';
1604     if (!file_exists($testfile)) {
1605         file_put_contents($testfile, 'test file, do not delete');
1606     }
1607     $teststr = trim(file_get_contents($testfile));
1608     if (empty($teststr)) {
1609         // hmm, strange
1610         return INSECURE_DATAROOT_WARNING;
1611     }
1613     $testurl = $datarooturl.'/diag/public.txt';
1615     if (extension_loaded('curl') and ($ch = @curl_init($testurl)) !== false) {
1616         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1617         curl_setopt($ch, CURLOPT_HEADER, false);
1618         $data = curl_exec($ch);
1619         if (!curl_errno($ch)) {
1620             $data = trim($data);
1621             if ($data === $teststr) {
1622                 curl_close($ch);
1623                 return INSECURE_DATAROOT_ERROR;
1624             }
1625         }
1626         curl_close($ch);
1627     }
1629     if ($data = @file_get_contents($testurl)) {
1630         $data = trim($data);
1631         if ($data === $teststr) {
1632             return INSECURE_DATAROOT_ERROR;
1633         }
1634     }
1636     preg_match('|https?://([^/]+)|i', $testurl, $matches);
1637     $sitename = $matches[1];
1638     $error = 0;
1639     if ($fp = @fsockopen($sitename, 80, $error)) {
1640         preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
1641         $localurl = $matches[1];
1642         $out = "GET $localurl HTTP/1.1\r\n";
1643         $out .= "Host: $sitename\r\n";
1644         $out .= "Connection: Close\r\n\r\n";
1645         fwrite($fp, $out);
1646         $data = '';
1647         $incoming = false;
1648         while (!feof($fp)) {
1649             if ($incoming) {
1650                 $data .= fgets($fp, 1024);
1651             } else if (@fgets($fp, 1024) === "\r\n") {
1652                 $incoming = true;
1653             }
1654         }
1655         fclose($fp);
1656         $data = trim($data);
1657         if ($data === $teststr) {
1658             return INSECURE_DATAROOT_ERROR;
1659         }
1660     }
1662     return INSECURE_DATAROOT_WARNING;
1665 /// =============================================================================================================
1666 /// administration tree classes and functions
1669 // n.b. documentation is still in progress for this code
1671 /// INTRODUCTION
1673 /// This file performs the following tasks:
1674 ///  -it defines the necessary objects and interfaces to build the Moodle
1675 ///   admin hierarchy
1676 ///  -it defines the admin_externalpage_setup(), admin_externalpage_print_header(),
1677 ///   and admin_externalpage_print_footer() functions used on admin pages
1679 /// ADMIN_SETTING OBJECTS
1681 /// Moodle settings are represented by objects that inherit from the admin_setting
1682 /// class. These objects encapsulate how to read a setting, how to write a new value
1683 /// to a setting, and how to appropriately display the HTML to modify the setting.
1685 /// ADMIN_SETTINGPAGE OBJECTS
1687 /// The admin_setting objects are then grouped into admin_settingpages. The latter
1688 /// appear in the Moodle admin tree block. All interaction with admin_settingpage
1689 /// objects is handled by the admin/settings.php file.
1691 /// ADMIN_EXTERNALPAGE OBJECTS
1693 /// There are some settings in Moodle that are too complex to (efficiently) handle
1694 /// with admin_settingpages. (Consider, for example, user management and displaying
1695 /// lists of users.) In this case, we use the admin_externalpage object. This object
1696 /// places a link to an external PHP file in the admin tree block.
1698 /// If you're using an admin_externalpage object for some settings, you can take
1699 /// advantage of the admin_externalpage_* functions. For example, suppose you wanted
1700 /// to add a foo.php file into admin. First off, you add the following line to
1701 /// admin/settings/first.php (at the end of the file) or to some other file in
1702 /// admin/settings:
1704 ///    $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
1705 ///        $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
1707 /// Next, in foo.php, your file structure would resemble the following:
1709 ///        require_once('.../config.php');
1710 ///        require_once($CFG->libdir.'/adminlib.php');
1711 ///        admin_externalpage_setup('foo');
1712 ///        // functionality like processing form submissions goes here
1713 ///        admin_externalpage_print_header();
1714 ///        // your HTML goes here
1715 ///        admin_externalpage_print_footer();
1717 /// The admin_externalpage_setup() function call ensures the user is logged in,
1718 /// and makes sure that they have the proper role permission to access the page.
1720 /// The admin_externalpage_print_header() function prints the header (it figures
1721 /// out what category and subcategories the page is classified under) and ensures
1722 /// that you're using the admin pagelib (which provides the admin tree block and
1723 /// the admin bookmarks block).
1725 /// The admin_externalpage_print_footer() function properly closes the tables
1726 /// opened up by the admin_externalpage_print_header() function and prints the
1727 /// standard Moodle footer.
1729 /// ADMIN_CATEGORY OBJECTS
1731 /// Above and beyond all this, we have admin_category objects. These objects
1732 /// appear as folders in the admin tree block. They contain admin_settingpage's,
1733 /// admin_externalpage's, and other admin_category's.
1735 /// OTHER NOTES
1737 /// admin_settingpage's, admin_externalpage's, and admin_category's all inherit
1738 /// from part_of_admin_tree (a pseudointerface). This interface insists that
1739 /// a class has a check_access method for access permissions, a locate method
1740 /// used to find a specific node in the admin tree and find parent path.
1742 /// admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
1743 /// interface ensures that the class implements a recursive add function which
1744 /// accepts a part_of_admin_tree object and searches for the proper place to
1745 /// put it. parentable_part_of_admin_tree implies part_of_admin_tree.
1747 /// Please note that the $this->name field of any part_of_admin_tree must be
1748 /// UNIQUE throughout the ENTIRE admin tree.
1750 /// The $this->name field of an admin_setting object (which is *not* part_of_
1751 /// admin_tree) must be unique on the respective admin_settingpage where it is
1752 /// used.
1755 /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
1757 /**
1758  * Pseudointerface for anything appearing in the admin tree
1759  *
1760  * The pseudointerface that is implemented by anything that appears in the admin tree
1761  * block. It forces inheriting classes to define a method for checking user permissions
1762  * and methods for finding something in the admin tree.
1763  *
1764  * @author Vincenzo K. Marcovecchio
1765  * @package admin
1766  */
1767 class part_of_admin_tree {
1769     /**
1770      * Finds a named part_of_admin_tree.
1771      *
1772      * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
1773      * and not parentable_part_of_admin_tree, then this function should only check if
1774      * $this->name matches $name. If it does, it should return a reference to $this,
1775      * otherwise, it should return a reference to NULL.
1776      *
1777      * If a class inherits parentable_part_of_admin_tree, this method should be called
1778      * recursively on all child objects (assuming, of course, the parent object's name
1779      * doesn't match the search criterion).
1780      *
1781      * @param string $name The internal name of the part_of_admin_tree we're searching for.
1782      * @return mixed An object reference or a NULL reference.
1783      */
1784     function &locate($name) {
1785         trigger_error('Admin class does not implement method <strong>locate()</strong>', E_USER_WARNING);
1786         return;
1787     }
1789     /**
1790      * Removes named part_of_admin_tree.
1791      *
1792      * @param string $name The internal name of the part_of_admin_tree we want to remove.
1793      * @return bool success.
1794      */
1795     function prune($name) {
1796         trigger_error('Admin class does not implement method <strong>prune()</strong>', E_USER_WARNING);
1797         return;
1798     }
1800     /**
1801      * Search using query
1802      * @param strin query
1803      * @return mixed array-object structure of found settings and pages
1804      */
1805     function search($query) {
1806         trigger_error('Admin class does not implement method <strong>search()</strong>', E_USER_WARNING);
1807         return;
1808     }
1810     /**
1811      * Verifies current user's access to this part_of_admin_tree.
1812      *
1813      * Used to check if the current user has access to this part of the admin tree or
1814      * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
1815      * then this method is usually just a call to has_capability() in the site context.
1816      *
1817      * If a class inherits parentable_part_of_admin_tree, this method should return the
1818      * logical OR of the return of check_access() on all child objects.
1819      *
1820      * @return bool True if the user has access, false if she doesn't.
1821      */
1822     function check_access() {
1823         trigger_error('Admin class does not implement method <strong>check_access()</strong>', E_USER_WARNING);
1824         return;
1825     }
1827     /**
1828      * Mostly usefull for removing of some parts of the tree in admin tree block.
1829      *
1830      * @return True is hidden from normal list view
1831      */
1832     function is_hidden() {
1833         trigger_error('Admin class does not implement method <strong>is_hidden()</strong>', E_USER_WARNING);
1834         return;
1835     }
1838 /**
1839  * Pseudointerface implemented by any part_of_admin_tree that has children.
1840  *
1841  * The pseudointerface implemented by any part_of_admin_tree that can be a parent
1842  * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
1843  * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
1844  * include an add method for adding other part_of_admin_tree objects as children.
1845  *
1846  * @author Vincenzo K. Marcovecchio
1847  * @package admin
1848  */
1849 class parentable_part_of_admin_tree extends part_of_admin_tree {
1851     /**
1852      * Adds a part_of_admin_tree object to the admin tree.
1853      *
1854      * Used to add a part_of_admin_tree object to this object or a child of this
1855      * object. $something should only be added if $destinationname matches
1856      * $this->name. If it doesn't, add should be called on child objects that are
1857      * also parentable_part_of_admin_tree's.
1858      *
1859      * @param string $destinationname The internal name of the new parent for $something.
1860      * @param part_of_admin_tree &$something The object to be added.
1861      * @return bool True on success, false on failure.
1862      */
1863     function add($destinationname, $something) {
1864         trigger_error('Admin class does not implement method <strong>add()</strong>', E_USER_WARNING);
1865         return;
1866     }
1870 /**
1871  * The object used to represent folders (a.k.a. categories) in the admin tree block.
1872  *
1873  * Each admin_category object contains a number of part_of_admin_tree objects.
1874  *
1875  * @author Vincenzo K. Marcovecchio
1876  * @package admin
1877  */
1878 class admin_category extends parentable_part_of_admin_tree {
1880     /**
1881      * @var mixed An array of part_of_admin_tree objects that are this object's children
1882      */
1883     var $children;
1885     /**
1886      * @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1887      */
1888     var $name;
1890     /**
1891      * @var string The displayed name for this category. Usually obtained through get_string()
1892      */
1893     var $visiblename;
1895     /**
1896      * @var bool Should this category be hidden in admin tree block?
1897      */
1898     var $hidden;
1900     /**
1901      * paths
1902      */
1903     var $path;
1904     var $visiblepath;
1906     /**
1907      * Constructor for an empty admin category
1908      *
1909      * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1910      * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
1911      * @param bool $hidden hide category in admin tree block
1912      */
1913     function admin_category($name, $visiblename, $hidden=false) {
1914         $this->children    = array();
1915         $this->name        = $name;
1916         $this->visiblename = $visiblename;
1917         $this->hidden      = $hidden;
1918     }
1920     /**
1921      * Returns a reference to the part_of_admin_tree object with internal name $name.
1922      *
1923      * @param string $name The internal name of the object we want.
1924      * @param bool $findpath initialize path and visiblepath arrays
1925      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1926      */
1927     function &locate($name, $findpath=false) {
1928         if ($this->name == $name) {
1929             if ($findpath) {
1930                 $this->visiblepath[] = $this->visiblename;
1931                 $this->path[]        = $this->name;
1932             }
1933             return $this;
1934         }
1936         $return = NULL;
1937         foreach($this->children as $childid=>$unused) {
1938             if ($return =& $this->children[$childid]->locate($name, $findpath)) {
1939                 break;
1940             }
1941         }
1943         if (!is_null($return) and $findpath) {
1944             $return->visiblepath[] = $this->visiblename;
1945             $return->path[]        = $this->name;
1946         }
1948         return $return;
1949     }
1951     /**
1952      * Search using query
1953      * @param strin query
1954      * @return mixed array-object structure of found settings and pages
1955      */
1956     function search($query) {
1957         $result = array();
1958         foreach ($this->children as $child) {
1959             $subsearch = $child->search($query);
1960             if (!is_array($subsearch)) {
1961                 debugging('Incorrect search result from '.$child->name);
1962                 continue;
1963             }
1964             $result = array_merge($result, $subsearch);
1965         }
1966         return $result;
1967     }
1969     /**
1970      * Removes part_of_admin_tree object with internal name $name.
1971      *
1972      * @param string $name The internal name of the object we want to remove.
1973      * @return bool success
1974      */
1975     function prune($name) {
1977         if ($this->name == $name) {
1978             return false;  //can not remove itself
1979         }
1981         foreach($this->children as $precedence => $child) {
1982             if ($child->name == $name) {
1983                 // found it!
1984                 unset($this->children[$precedence]);
1985                 return true;
1986             }
1987             if ($this->children[$precedence]->prune($name)) {
1988                 return true;
1989             }
1990         }
1991         return false;
1992     }
1994     /**
1995      * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
1996      *
1997      * @param string $destinationame The internal name of the immediate parent that we want for $something.
1998      * @param mixed $something A part_of_admin_tree or setting instanceto be added.
1999      * @return bool True if successfully added, false if $something can not be added.
2000      */
2001     function add($parentname, $something) {
2002         $parent =& $this->locate($parentname);
2003         if (is_null($parent)) {
2004             debugging('parent does not exist!');
2005             return false;
2006         }
2008         if (is_a($something, 'part_of_admin_tree')) {
2009             if (!is_a($parent, 'parentable_part_of_admin_tree')) {
2010                 debugging('error - parts of tree can be inserted only into parentable parts');
2011                 return false;
2012             }
2013             $parent->children[] = $something;
2014             return true;
2016         } else {
2017             debugging('error - can not add this element');
2018             return false;
2019         }
2021     }
2023     /**
2024      * Checks if the user has access to anything in this category.
2025      *
2026      * @return bool True if the user has access to atleast one child in this category, false otherwise.
2027      */
2028     function check_access() {
2029         foreach ($this->children as $child) {
2030             if ($child->check_access()) {
2031                 return true;
2032             }
2033         }
2034         return false;
2035     }
2037     /**
2038      * Is this category hidden in admin tree block?
2039      *
2040      * @return bool True if hidden
2041      */
2042     function is_hidden() {
2043         return $this->hidden;
2044     }
2047 class admin_root extends admin_category {
2048     /**
2049      * list of errors
2050      */
2051     var $errors;
2053     /**
2054      * search query
2055      */
2056     var $search;
2058     /**
2059      * full tree flag - true means all settings required, false onlypages required
2060      */
2061     var $fulltree;
2064     function admin_root() {
2065         parent::admin_category('root', get_string('administration'), false);
2066         $this->errors   = array();
2067         $this->search   = '';
2068         $this->fulltree = true;
2069     }
2072 /**
2073  * Links external PHP pages into the admin tree.
2074  *
2075  * See detailed usage example at the top of this document (adminlib.php)
2076  *
2077  * @author Vincenzo K. Marcovecchio
2078  * @package admin
2079  */
2080 class admin_externalpage extends part_of_admin_tree {
2082     /**
2083      * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
2084      */
2085     var $name;
2087     /**
2088      * @var string The displayed name for this external page. Usually obtained through get_string().
2089      */
2090     var $visiblename;
2092     /**
2093      * @var string The external URL that we should link to when someone requests this external page.
2094      */
2095     var $url;
2097     /**
2098      * @var string The role capability/permission a user must have to access this external page.
2099      */
2100     var $req_capability;
2102     /**
2103      * @var object The context in which capability/permission should be checked, default is site context.
2104      */
2105     var $context;
2107     /**
2108      * @var bool hidden in admin tree block.
2109      */
2110     var $hidden;
2112     /**
2113      * visible path
2114      */
2115     var $path;
2116     var $visiblepath;
2118     /**
2119      * Constructor for adding an external page into the admin tree.
2120      *
2121      * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
2122      * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
2123      * @param string $url The external URL that we should link to when someone requests this external page.
2124      * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
2125      * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
2126      * @param context $context The context the page relates to. Not sure what happens
2127      *      if you specify something other than system or front page. Defaults to system.
2128      */
2129     function admin_externalpage($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
2130         $this->name        = $name;
2131         $this->visiblename = $visiblename;
2132         $this->url         = $url;
2133         if (is_array($req_capability)) {
2134             $this->req_capability = $req_capability;
2135         } else {
2136             $this->req_capability = array($req_capability);
2137         }
2138         $this->hidden = $hidden;
2139         $this->context = $context;
2140     }
2142     /**
2143      * Returns a reference to the part_of_admin_tree object with internal name $name.
2144      *
2145      * @param string $name The internal name of the object we want.
2146      * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
2147      */
2148     function &locate($name, $findpath=false) {
2149         if ($this->name == $name) {
2150             if ($findpath) {
2151                 $this->visiblepath = array($this->visiblename);
2152                 $this->path        = array($this->name);
2153             }
2154             return $this;
2155         } else {
2156             $return = NULL;
2157             return $return;
2158         }
2159     }
2161     function prune($name) {
2162         return false;
2163     }
2165     /**
2166      * Search using query
2167      * @param strin query
2168      * @return mixed array-object structure of found settings and pages
2169      */
2170     function search($query) {
2171         $textlib = textlib_get_instance();
2173         $found = false;
2174         if (strpos(strtolower($this->name), $query) !== false) {
2175             $found = true;
2176         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
2177             $found = true;
2178         }
2179         if ($found) {
2180             $result = new object();
2181             $result->page     = $this;
2182             $result->settings = array();
2183             return array($this->name => $result);
2184         } else {
2185             return array();
2186         }
2187     }
2189     /**
2190      * Determines if the current user has access to this external page based on $this->req_capability.
2191      * @return bool True if user has access, false otherwise.
2192      */
2193     function check_access() {
2194         if (!get_site()) {
2195             return true; // no access check before site is fully set up
2196         }
2197         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
2198         foreach($this->req_capability as $cap) {
2199             if (is_valid_capability($cap) and has_capability($cap, $context)) {
2200                 return true;
2201             }
2202         }
2203         return false;
2204     }
2206     /**
2207      * Is this external page hidden in admin tree block?
2208      *
2209      * @return bool True if hidden
2210      */
2211     function is_hidden() {
2212         return $this->hidden;
2213     }
2217 /**
2218  * Used to group a number of admin_setting objects into a page and add them to the admin tree.
2219  *
2220  * @author Vincenzo K. Marcovecchio
2221  * @package admin
2222  */
2223 class admin_settingpage extends part_of_admin_tree {
2225     /**
2226      * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
2227      */
2228     var $name;
2230     /**
2231      * @var string The displayed name for this external page. Usually obtained through get_string().
2232      */
2233     var $visiblename;
2234     /**
2235      * @var mixed An array of admin_setting objects that are part of this setting page.
2236      */
2237     var $settings;
2239     /**
2240      * @var string The role capability/permission a user must have to access this external page.
2241      */
2242     var $req_capability;
2244     /**
2245      * @var object The context in which capability/permission should be checked, default is site context.
2246      */
2247     var $context;
2249     /**
2250      * @var bool hidden in admin tree block.
2251      */
2252     var $hidden;
2254     /**
2255      * paths
2256      */
2257     var $path;
2258     var $visiblepath;
2260     // see admin_externalpage
2261     function admin_settingpage($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
2262         $this->settings    = new object();
2263         $this->name        = $name;
2264         $this->visiblename = $visiblename;
2265         if (is_array($req_capability)) {
2266             $this->req_capability = $req_capability;
2267         } else {
2268             $this->req_capability = array($req_capability);
2269         }
2270         $this->hidden      = $hidden;
2271         $this->context     = $context;
2272     }
2274     // see admin_category
2275     function &locate($name, $findpath=false) {
2276         if ($this->name == $name) {
2277             if ($findpath) {
2278                 $this->visiblepath = array($this->visiblename);
2279                 $this->path        = array($this->name);
2280             }
2281             return $this;
2282         } else {
2283             $return = NULL;
2284             return $return;
2285         }
2286     }
2288     function search($query) {
2289         $found = array();
2291         foreach ($this->settings as $setting) {
2292             if ($setting->is_related($query)) {
2293                 $found[] = $setting;
2294             }
2295         }
2297         if ($found) {
2298             $result = new object();
2299             $result->page     = $this;
2300             $result->settings = $found;
2301             return array($this->name => $result);
2302         }
2304         $textlib = textlib_get_instance();
2306         $found = false;
2307         if (strpos(strtolower($this->name), $query) !== false) {
2308             $found = true;
2309         } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
2310             $found = true;
2311         }
2312         if ($found) {
2313             $result = new object();
2314             $result->page     = $this;
2315             $result->settings = array();
2316             return array($this->name => $result);
2317         } else {
2318             return array();
2319         }
2320     }
2322     function prune($name) {
2323         return false;
2324     }
2326     /**
2327      * 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
2328      * n.b. each admin_setting in an admin_settingpage must have a unique internal name
2329      * @param object $setting is the admin_setting object you want to add
2330      * @return true if successful, false if not
2331      */
2332     function add($setting) {
2333         if (!is_a($setting, 'admin_setting')) {
2334             debugging('error - not a setting instance');
2335             return false;
2336         }
2338         $this->settings->{$setting->name} = $setting;
2339         return true;
2340     }
2342     // see admin_externalpage
2343     function check_access() {
2344         if (!get_site()) {
2345             return true; // no access check before site is fully set up
2346         }
2347         $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
2348         foreach($this->req_capability as $cap) {
2349             if (is_valid_capability($cap) and has_capability($cap, $context)) {
2350                 return true;
2351             }
2352         }
2353         return false;
2354     }
2356     /**
2357      * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
2358      * returns a string of the html
2359      */
2360     function output_html() {
2361         $adminroot =& admin_get_root();
2362         $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
2363         foreach($this->settings as $setting) {
2364             $fullname = $setting->get_full_name();
2365             if (array_key_exists($fullname, $adminroot->errors)) {
2366                 $data = $adminroot->errors[$fullname]->data;
2367             } else {
2368                 $data = $setting->get_setting();
2369                 // do not use defaults if settings not available - upgrdesettings handles the defaults!
2370             }
2371             $return .= $setting->output_html($data);
2372         }
2373         $return .= '</fieldset>';
2374         return $return;
2375     }
2377     /**
2378      * Is this settigns page hidden in admin tree block?
2379      *
2380      * @return bool True if hidden
2381      */
2382     function is_hidden() {
2383         return $this->hidden;
2384     }
2389 /**
2390  * Admin settings class. Only exists on setting pages.
2391  * Read & write happens at this level; no authentication.
2392  */
2393 class admin_setting {
2395     var $name;
2396     var $visiblename;
2397     var $description;
2398     var $defaultsetting;
2399     var $updatedcallback;
2400     var $plugin; // null means main config table
2402     /**
2403      * Constructor
2404      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2405      * @param string $visiblename localised name
2406      * @param string $description localised long description
2407      * @param mixed $defaultsetting string or array depending on implementation
2408      */
2409     function admin_setting($name, $visiblename, $description, $defaultsetting) {
2410         $this->parse_setting_name($name);
2411         $this->visiblename    = $visiblename;
2412         $this->description    = $description;
2413         $this->defaultsetting = $defaultsetting;
2414     }
2416     /**
2417      * Set up $this->name and possibly $this->plugin based on whether $name looks
2418      * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
2419      * on the names, that is, output a developer debug warning if the name
2420      * contains anything other than [a-zA-Z0-9_]+.
2421      *
2422      * @param string $name the setting name passed in to the constructor.
2423      */
2424     private function parse_setting_name($name) {
2425         $bits = explode('/', $name);
2426         if (count($bits) > 2) {
2427             throw new moodle_exception('invalidadminsettingname', '', '', $name);
2428         }
2429         $this->name = array_pop($bits);
2430         if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
2431             throw new moodle_exception('invalidadminsettingname', '', '', $name);
2432         }
2433         if (!empty($bits)) {
2434             $this->plugin = array_pop($bits);
2435             if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
2436                 throw new moodle_exception('invalidadminsettingname', '', '', $name);
2437             }
2438         }
2439     }
2441     function get_full_name() {
2442         return 's_'.$this->plugin.'_'.$this->name;
2443     }
2445     function get_id() {
2446         return 'id_s_'.$this->plugin.'_'.$this->name;
2447     }
2449     function config_read($name) {
2450         global $CFG;
2451         if ($this->plugin === 'backup') {
2452             require_once($CFG->dirroot.'/backup/lib.php');
2453             $backupconfig = backup_get_config();
2454             if (isset($backupconfig->$name)) {
2455                 return $backupconfig->$name;
2456             } else {
2457                 return NULL;
2458             }
2460         } else if (!empty($this->plugin)) {
2461             $value = get_config($this->plugin, $name);
2462             return $value === false ? NULL : $value;
2464         } else {
2465             if (isset($CFG->$name)) {
2466                 return $CFG->$name;
2467             } else {
2468                 return NULL;
2469             }
2470         }
2471     }
2473     function config_write($name, $value) {
2474         global $CFG;
2475         if ($this->plugin === 'backup') {
2476             require_once($CFG->dirroot.'/backup/lib.php');
2477             return (boolean)backup_set_config($name, $value);
2478         } else {
2479             return (boolean)set_config($name, $value, $this->plugin);
2480         }
2481     }
2483     /**
2484      * Returns current value of this setting
2485      * @return mixed array or string depending on instance, NULL means not set yet
2486      */
2487     function get_setting() {
2488         // has to be overridden
2489         return NULL;
2490     }
2492     /**
2493      * Returns default setting if exists
2494      * @return mixed array or string depending on instance; NULL means no default, user must supply
2495      */
2496     function get_defaultsetting() {
2497         return $this->defaultsetting;
2498     }
2500     /**
2501      * Store new setting
2502      * @param mixed string or array, must not be NULL
2503      * @return '' if ok, string error message otherwise
2504      */
2505     function write_setting($data) {
2506         // should be overridden
2507         return '';
2508     }
2510     /**
2511      * Return part of form with setting
2512      * @param mixed data array or string depending on setting
2513      * @return string
2514      */
2515     function output_html($data, $query='') {
2516         // should be overridden
2517         return;
2518     }
2520     /**
2521      * function called if setting updated - cleanup, cache reset, etc.
2522      */
2523     function set_updatedcallback($functionname) {
2524         $this->updatedcallback = $functionname;
2525     }
2527     /**
2528      * Is setting related to query text - used when searching
2529      * @param string $query
2530      * @return bool
2531      */
2532     function is_related($query) {
2533         if (strpos(strtolower($this->name), $query) !== false) {
2534             return true;
2535         }
2536         $textlib = textlib_get_instance();
2537         if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
2538             return true;
2539         }
2540         if (strpos($textlib->strtolower($this->description), $query) !== false) {
2541             return true;
2542         }
2543         $current = $this->get_setting();
2544         if (!is_null($current)) {
2545             if (is_string($current)) {
2546                 if (strpos($textlib->strtolower($current), $query) !== false) {
2547                     return true;
2548                 }
2549             }
2550         }
2551         $default = $this->get_defaultsetting();
2552         if (!is_null($default)) {
2553             if (is_string($default)) {
2554                 if (strpos($textlib->strtolower($default), $query) !== false) {
2555                     return true;
2556                 }
2557             }
2558         }
2559         return false;
2560     }
2563 /**
2564  * No setting - just heading and text.
2565  */
2566 class admin_setting_heading extends admin_setting {
2567     /**
2568      * not a setting, just text
2569      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2570      * @param string $heading heading
2571      * @param string $information text in box
2572      */
2573     function admin_setting_heading($name, $heading, $information) {
2574         parent::admin_setting($name, $heading, $information, '');
2575     }
2577     function get_setting() {
2578         return true;
2579     }
2581     function get_defaultsetting() {
2582         return true;
2583     }
2585     function write_setting($data) {
2586         // do not write any setting
2587         return '';
2588     }
2590     function output_html($data, $query='') {
2591         $return = '';
2592         if ($this->visiblename != '') {
2593             $return .= print_heading('<a name="'.$this->name.'">'.highlightfast($query, $this->visiblename).'</a>', '', 3, 'main', true);
2594         }
2595         if ($this->description != '') {
2596             $return .= print_box(highlight($query, $this->description), 'generalbox formsettingheading', '', true);
2597         }
2598         return $return;
2599     }
2602 /**
2603  * The most flexibly setting, user is typing text
2604  */
2605 class admin_setting_configtext extends admin_setting {
2607     var $paramtype;
2608     var $size;
2610     /**
2611      * config text contructor
2612      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2613      * @param string $visiblename localised
2614      * @param string $description long localised info
2615      * @param string $defaultsetting
2616      * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
2617      * @param int $size default field size
2618      */
2619     function admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
2620         $this->paramtype = $paramtype;
2621         if (!is_null($size)) {
2622             $this->size  = $size;
2623         } else {
2624             $this->size  = ($paramtype == PARAM_INT) ? 5 : 30;
2625         }
2626         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2627     }
2629     function get_setting() {
2630         return $this->config_read($this->name);
2631     }
2633     function write_setting($data) {
2634         if ($this->paramtype === PARAM_INT and $data === '') {
2635             // do not complain if '' used instead of 0
2636             $data = 0;
2637         }
2638         // $data is a string
2639         $validated = $this->validate($data);
2640         if ($validated !== true) {
2641             return $validated;
2642         }
2643         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2644     }
2646     /**
2647      * Validate data before storage
2648      * @param string data
2649      * @return mixed true if ok string if error found
2650      */
2651     function validate($data) {
2652         if (is_string($this->paramtype)) {
2653             if (preg_match($this->paramtype, $data)) {
2654                 return true;
2655             } else {
2656                 return get_string('validateerror', 'admin');
2657             }
2659         } else if ($this->paramtype === PARAM_RAW) {
2660             return true;
2662         } else {
2663             $cleaned = clean_param($data, $this->paramtype);
2664             if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
2665                 return true;
2666             } else {
2667                 return get_string('validateerror', 'admin');
2668             }
2669         }
2670     }
2672     function output_html($data, $query='') {
2673         $default = $this->get_defaultsetting();
2675         return format_admin_setting($this, $this->visiblename,
2676                 '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
2677                 $this->description, true, '', $default, $query);
2678     }
2681 /**
2682  * General text area without html editor.
2683  */
2684 class admin_setting_configtextarea extends admin_setting_configtext {
2685     var $rows;
2686     var $cols;
2688     function admin_setting_configtextarea($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2689         $this->rows = $rows;
2690         $this->cols = $cols;
2691         parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype);
2692     }
2694     function output_html($data, $query='') {
2695         $default = $this->get_defaultsetting();
2697         $defaultinfo = $default;
2698         if (!is_null($default) and $default !== '') {
2699             $defaultinfo = "\n".$default;
2700         }
2702         return format_admin_setting($this, $this->visiblename,
2703                 '<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>',
2704                 $this->description, true, '', $defaultinfo, $query);
2705     }
2708 /**
2709  * Password field, allows unmasking of password
2710  */
2711 class admin_setting_configpasswordunmask extends admin_setting_configtext {
2712     /**
2713      * Constructor
2714      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2715      * @param string $visiblename localised
2716      * @param string $description long localised info
2717      * @param string $defaultsetting default password
2718      */
2719     function admin_setting_configpasswordunmask($name, $visiblename, $description, $defaultsetting) {
2720         parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
2721     }
2723     function output_html($data, $query='') {
2724         $id = $this->get_id();
2725         $unmask = get_string('unmaskpassword', 'form');
2726         $unmaskjs = '<script type="text/javascript">
2727 //<![CDATA[
2728 var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
2730 document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
2732 var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
2734 var unmaskchb = document.createElement("input");
2735 unmaskchb.setAttribute("type", "checkbox");
2736 unmaskchb.setAttribute("id", "'.$id.'unmask");
2737 unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
2738 unmaskdiv.appendChild(unmaskchb);
2740 var unmasklbl = document.createElement("label");
2741 unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
2742 if (is_ie) {
2743   unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
2744 } else {
2745   unmasklbl.setAttribute("for", "'.$id.'unmask");
2747 unmaskdiv.appendChild(unmasklbl);
2749 if (is_ie) {
2750   // ugly hack to work around the famous onchange IE bug
2751   unmaskchb.onclick = function() {this.blur();};
2752   unmaskdiv.onclick = function() {this.blur();};
2754 //]]>
2755 </script>';
2756         return format_admin_setting($this, $this->visiblename,
2757                 '<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>',
2758                 $this->description, true, '', NULL, $query);
2759     }
2762 /**
2763  * Path to directory
2764  */
2765 class admin_setting_configfile extends admin_setting_configtext {
2766     /**
2767      * Constructor
2768      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2769      * @param string $visiblename localised
2770      * @param string $description long localised info
2771      * @param string $defaultdirectory default directory location
2772      */
2773     function admin_setting_configfile($name, $visiblename, $description, $defaultdirectory) {
2774         parent::admin_setting_configtext($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
2775     }
2777     function output_html($data, $query='') {
2778         $default = $this->get_defaultsetting();
2780         if ($data) {
2781             if (file_exists($data)) {
2782                 $executable = '<span class="pathok">&#x2714;</span>';
2783             } else {
2784                 $executable = '<span class="patherror">&#x2718;</span>';
2785             }
2786         } else {
2787             $executable = '';
2788         }
2790         return format_admin_setting($this, $this->visiblename,
2791                 '<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>',
2792                 $this->description, true, '', $default, $query);
2793     }
2796 /**
2797  * Path to executable file
2798  */
2799 class admin_setting_configexecutable extends admin_setting_configfile {
2801     function output_html($data, $query='') {
2802         $default = $this->get_defaultsetting();
2804         if ($data) {
2805             if (file_exists($data) and is_executable($data)) {
2806                 $executable = '<span class="pathok">&#x2714;</span>';
2807             } else {
2808                 $executable = '<span class="patherror">&#x2718;</span>';
2809             }
2810         } else {
2811             $executable = '';
2812         }
2814         return format_admin_setting($this, $this->visiblename,
2815                 '<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>',
2816                 $this->description, true, '', $default, $query);
2817     }
2820 /**
2821  * Path to directory
2822  */
2823 class admin_setting_configdirectory extends admin_setting_configfile {
2824     function output_html($data, $query='') {
2825         $default = $this->get_defaultsetting();
2827         if ($data) {
2828             if (file_exists($data) and is_dir($data)) {
2829                 $executable = '<span class="pathok">&#x2714;</span>';
2830             } else {
2831                 $executable = '<span class="patherror">&#x2718;</span>';
2832             }
2833         } else {
2834             $executable = '';
2835         }
2837         return format_admin_setting($this, $this->visiblename,
2838                 '<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>',
2839                 $this->description, true, '', $default, $query);
2840     }
2843 /**
2844  * Checkbox
2845  */
2846 class admin_setting_configcheckbox extends admin_setting {
2847     var $yes;
2848     var $no;
2850     /**
2851      * Constructor
2852      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2853      * @param string $visiblename localised
2854      * @param string $description long localised info
2855      * @param string $defaultsetting
2856      * @param string $yes value used when checked
2857      * @param string $no value used when not checked
2858      */
2859     function admin_setting_configcheckbox($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2860         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2861         $this->yes = (string)$yes;
2862         $this->no  = (string)$no;
2863     }
2865     function get_setting() {
2866         return $this->config_read($this->name);
2867     }
2869     function write_setting($data) {
2870         if ((string)$data === $this->yes) { // convert to strings before comparison
2871             $data = $this->yes;
2872         } else {
2873             $data = $this->no;
2874         }
2875         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
2876     }
2878     function output_html($data, $query='') {
2879         $default = $this->get_defaultsetting();
2881         if (!is_null($default)) {
2882             if ((string)$default === $this->yes) {
2883                 $defaultinfo = get_string('checkboxyes', 'admin');
2884             } else {
2885                 $defaultinfo = get_string('checkboxno', 'admin');
2886             }
2887         } else {
2888             $defaultinfo = NULL;
2889         }
2891         if ((string)$data === $this->yes) { // convert to strings before comparison
2892             $checked = 'checked="checked"';
2893         } else {
2894             $checked = '';
2895         }
2897         return format_admin_setting($this, $this->visiblename,
2898                 '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2899                 .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2900                 $this->description, true, '', $defaultinfo, $query);
2901     }
2904 /**
2905  * Multiple checkboxes, each represents different value, stored in csv format
2906  */
2907 class admin_setting_configmulticheckbox extends admin_setting {
2908     var $choices;
2910     /**
2911      * Constructor
2912      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2913      * @param string $visiblename localised
2914      * @param string $description long localised info
2915      * @param array $defaultsetting array of selected
2916      * @param array $choices array of $value=>$label for each checkbox
2917      */
2918     function admin_setting_configmulticheckbox($name, $visiblename, $description, $defaultsetting, $choices) {
2919         $this->choices = $choices;
2920         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2921     }
2923     /**
2924      * This function may be used in ancestors for lazy loading of choices
2925      * @return true if loaded, false if error
2926      */
2927     function load_choices() {
2928         /*
2929         if (is_array($this->choices)) {
2930             return true;
2931         }
2932         .... load choices here
2933         */
2934         return true;
2935     }
2937     /**
2938      * Is setting related to query text - used when searching
2939      * @param string $query
2940      * @return bool
2941      */
2942     function is_related($query) {
2943         if (!$this->load_choices() or empty($this->choices)) {
2944             return false;
2945         }
2946         if (parent::is_related($query)) {
2947             return true;
2948         }
2950         $textlib = textlib_get_instance();
2951         foreach ($this->choices as $desc) {
2952             if (strpos($textlib->strtolower($desc), $query) !== false) {
2953                 return true;
2954             }
2955         }
2956         return false;
2957     }
2959     function get_setting() {
2960         $result = $this->config_read($this->name);
2962         if (is_null($result)) {
2963             return NULL;
2964         }
2965         if ($result === '') {
2966             return array();
2967         }
2968         $enabled = explode(',', $result);
2969         $setting = array();
2970         foreach ($enabled as $option) {
2971             $setting[$option] = 1;
2972         }
2973         return $setting;
2974     }
2976     function write_setting($data) {
2977         if (!is_array($data)) {
2978             return ''; // ignore it
2979         }
2980         if (!$this->load_choices() or empty($this->choices)) {
2981             return '';
2982         }
2983         unset($data['xxxxx']);
2984         $result = array();
2985         foreach ($data as $key => $value) {
2986             if ($value and array_key_exists($key, $this->choices)) {
2987                 $result[] = $key;
2988             }
2989         }
2990         return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
2991     }
2993     function output_html($data, $query='') {
2994         if (!$this->load_choices() or empty($this->choices)) {
2995             return '';
2996         }
2997         $default = $this->get_defaultsetting();
2998         if (is_null($default)) {
2999             $default = array();
3000         }
3001         if (is_null($data)) {
3002             $data = array();
3003         }
3004         $options = array();
3005         $defaults = array();
3006         foreach ($this->choices as $key=>$description) {
3007             if (!empty($data[$key])) {
3008                 $checked = 'checked="checked"';
3009             } else {
3010                 $checked = '';
3011             }
3012             if (!empty($default[$key])) {
3013                 $defaults[] = $description;
3014             }
3016             $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
3017                          .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
3018         }
3020         if (is_null($default)) {
3021             $defaultinfo = NULL;
3022         } else if (!empty($defaults)) {
3023             $defaultinfo = implode(', ', $defaults);
3024         } else {
3025             $defaultinfo = get_string('none');
3026         }
3028         $return = '<div class="form-multicheckbox">';
3029         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3030         if ($options) {
3031             $return .= '<ul>';
3032             foreach ($options as $option) {
3033                 $return .= '<li>'.$option.'</li>';
3034             }
3035             $return .= '</ul>';
3036         }
3037         $return .= '</div>';
3039         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
3041     }
3044 /**
3045  * Multiple checkboxes 2, value stored as string 00101011
3046  */
3047 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
3048     function get_setting() {
3049         $result = $this->config_read($this->name);
3050         if (is_null($result)) {
3051             return NULL;
3052         }
3053         if (!$this->load_choices()) {
3054             return NULL;
3055         }
3056         $result = str_pad($result, count($this->choices), '0');
3057         $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
3058         $setting = array();
3059         foreach ($this->choices as $key=>$unused) {
3060             $value = array_shift($result);
3061             if ($value) {
3062                 $setting[$key] = 1;
3063             }
3064         }
3065         return $setting;
3066     }
3068     function write_setting($data) {
3069         if (!is_array($data)) {
3070             return ''; // ignore it
3071         }
3072         if (!$this->load_choices() or empty($this->choices)) {
3073             return '';
3074         }
3075         $result = '';
3076         foreach ($this->choices as $key=>$unused) {
3077             if (!empty($data[$key])) {
3078                 $result .= '1';
3079             } else {
3080                 $result .= '0';
3081             }
3082         }
3083         return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
3084     }
3087 /**
3088  * Select one value from list
3089  */
3090 class admin_setting_configselect extends admin_setting {
3091     var $choices;
3093     /**
3094      * Constructor
3095      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3096      * @param string $visiblename localised
3097      * @param string $description long localised info
3098      * @param string $defaultsetting
3099      * @param array $choices array of $value=>$label for each selection
3100      */
3101     function admin_setting_configselect($name, $visiblename, $description, $defaultsetting, $choices) {
3102         $this->choices = $choices;
3103         parent::admin_setting($name, $visiblename, $description, $defaultsetting);
3104     }
3106     /**
3107      * This function may be used in ancestors for lazy loading of choices
3108      * @return true if loaded, false if error
3109      */
3110     function load_choices() {
3111         /*
3112         if (is_array($this->choices)) {
3113             return true;
3114         }
3115         .... load choices here
3116         */
3117         return true;
3118     }
3120     function is_related($query) {
3121         if (parent::is_related($query)) {
3122             return true;
3123         }
3124         if (!$this->load_choices()) {
3125             return false;
3126         }
3127         $textlib = textlib_get_instance();
3128         foreach ($this->choices as $key=>$value) {
3129             if (strpos($textlib->strtolower($key), $query) !== false) {
3130                 return true;
3131             }
3132             if (strpos($textlib->strtolower($value), $query) !== false) {
3133                 return true;
3134             }
3135         }
3136         return false;
3137     }
3139     function get_setting() {
3140         return $this->config_read($this->name);
3141     }
3143     function write_setting($data) {
3144         if (!$this->load_choices() or empty($this->choices)) {
3145             return '';
3146         }
3147         if (!array_key_exists($data, $this->choices)) {
3148             return ''; // ignore it
3149         }
3151         return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
3152     }
3154     /**
3155      * Ensure the options are loaded, and generate the HTML for the select
3156      * element and any warning message. Separating this out from output_html
3157      * makes it easier to subclass this class.
3158      *
3159      * @param string $data the option to show as selected.
3160      * @param string $current the currently selected option in the database, null if none.
3161      * @param string $default the default selected option.
3162      * @return array the HTML for the select element, and a warning message.
3163      */
3164     function output_select_html($data, $current, $default, $extraname = '') {
3165         if (!$this->load_choices() or empty($this->choices)) {
3166             return array('', '');
3167         }
3169         $warning = '';
3170         if (is_null($current)) {
3171             // first run
3172         } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
3173             // no warning
3174         } else if (!array_key_exists($current, $this->choices)) {
3175             $warning = get_string('warningcurrentsetting', 'admin', s($current));
3176             if (!is_null($default) and $data == $current) {
3177                 $data = $default; // use default instead of first value when showing the form
3178             }
3179         }
3181         $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
3182         foreach ($this->choices as $key => $value) {
3183             // the string cast is needed because key may be integer - 0 is equal to most strings!
3184             $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
3185         }
3186         $selecthtml .= '</select>';
3187         return array($selecthtml, $warning);
3188     }
3190     function output_html($data, $query='') {
3191         $default = $this->get_defaultsetting();
3192         $current = $this->get_setting();
3194         list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
3195         if (!$selecthtml) {
3196             return '';
3197         }
3199         if (!is_null($default) and array_key_exists($default, $this->choices)) {
3200             $defaultinfo = $this->choices[$default];
3201         } else {
3202             $defaultinfo = NULL;
3203         }
3205         $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
3207         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
3208     }
3211 /**
3212  * Select multiple items from list
3213  */
3214 class admin_setting_configmultiselect extends admin_setting_configselect {
3215     /**
3216      * Constructor
3217      * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3218      * @param string $visiblename localised
3219      * @param string $description long localised info
3220      * @param array $defaultsetting array of selected items
3221      * @param array $choices array of $value=>$label for each list item
3222      */
3223     function admin_setting_configmultiselect($name, $visiblename, $description, $defaultsetting, $choices) {
3224         parent::admin_setting_configselect($name, $visiblename, $description, $defaultsetting, $choices);
3225     }
3227     function get_setting() {
3228         $result = $this->config_read($this->name);
3229         if (is_null($result)) {
3230             return NULL;
3231         }
3232         if ($result === '') {
3233             return array();
3234         }
3235         return explode(',', $result);
3236     }
3238     function write_setting($data) {
3239         if (!is_array($data)) {
3240             return ''; //ignore it
3241         }
3242         if (!$this->load_choices() or empty($this->choices)) {
3243             return '';
3244         }
3246         unset($data['xxxxx']);
3248         $save = array();
3249         foreach ($data as $value) {
3250             if (!array_key_exists($value, $this->choices)) {
3251                 continue; // ignore it
3252             }
3253             $save[] = $value;
3254         }
3256         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3257     }
3259     /**
3260      * Is setting related to query text - used when searching
3261      * @param string $query
3262      * @return bool
3263      */
3264     function is_related($query) {
3265         if (!$this->load_choices() or empty($this->choices)) {
3266             return false;
3267         }
3268         if (parent::is_related($query)) {
3269             return true;
3270         }
3272         $textlib = textlib_get_instance();
3273         foreach ($this->choices as $desc) {
3274             if (strpos($textlib->strtolower($desc), $query) !== false) {
3275                 return true;
3276             }
3277         }
3278         return false;
3279     }
3281     function output_html($data, $query='') {
3282         if (!$this->load_choices() or empty($this->choices)) {
3283             return '';
3284         }
3285         $choices = $this->choices;
3286         $default = $this->get_defaultsetting();
3287         if (is_null($default)) {
3288             $default = array();
3289         }
3290         if (is_null($data)) {
3291             $data = array();
3292         }
3294         $defaults = array();
3295         $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3296         $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="10" multiple="multiple">';
3297         foreach ($this->choices as $key => $description) {
3298             if (in_array($key, $data)) {
3299                 $selected = 'selected="selected"';
3300             } else {
3301                 $selected = '';
3302             }
3303             if (in_array($key, $default)) {
3304                 $defaults[] = $description;
3305             }
3307             $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
3308         }
3310         if (is_null($default)) {
3311             $defaultinfo = NULL;
3312         } if (!empty($defaults)) {
3313             $defaultinfo = implode(', ', $defaults);
3314         } else {
3315             $defaultinfo = get_string('none');
3316         }
3318         $return .= '</select></div>';
3319         return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
3320     }
3323 /**
3324  * Time selector
3325  * this is a liiitle bit messy. we're using two selects, but we're returning
3326  * them as an array named after $name (so we only use $name2 internally for the setting)
3327  */
3328 class admin_setting_configtime extends admin_setting {
3329     var $name2;
3331     /**
3332      * Constructor
3333      * @param string $hoursname setting for hours
3334      * @param string $minutesname setting for hours
3335      * @param string $visiblename localised
3336      * @param string $description long localised info
3337      * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3338      */
3339     function admin_setting_configtime($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
3340         $this->name2 = $minutesname;
3341         parent::admin_setting($hoursname, $visiblename, $description, $defaultsetting);
3342     }
3344     function get_setting() {
3345         $result1 = $this->config_read($this->name);
3346         $result2 = $this->config_read($this->name2);
3347         if (is_null($result1) or is_null($result2)) {
3348             return NULL;
3349         }
3351         return array('h' => $result1, 'm' => $result2);
3352     }
3354     function write_setting($data) {
3355         if (!is_array($data)) {
3356             return '';
3357         }
3359         $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3360         return ($result ? '' : get_string('errorsetting', 'admin'));
3361     }
3363     function output_html($data, $query='') {
3364         $default = $this->get_defaultsetting();
3366         if (is_array($default)) {
3367             $defaultinfo = $default['h'].':'.$default['m'];
3368         } else {
3369             $defaultinfo = NULL;
3370         }
3372         $return = '<div class="form-time defaultsnext">'.
3373                   '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
3374         for ($i = 0; $i < 24; $i++) {
3375             $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
3376         }
3377         $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
3378         for ($i = 0; $i < 60; $i += 5) {
3379             $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
3380         }
3381         $return .= '</select></div>';
3382         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
3383     }
3387 class admin_setting_configiplist extends admin_setting_configtextarea {
3388     function validate($data) {
3389         if(!empty($data)) {
3390             $ips = explode("\n", $data);
3391         } else {
3392             return true;
3393         }
3394         $result = true;
3395         foreach($ips as $ip) {
3396             $ip = trim($ip);
3397             if(preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
3398                    preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
3399                    preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
3400                 $result = true;
3401             } else {
3402                 $result = false;
3403                 break;
3404             }
3405         }
3406         if($result){
3407             return true;
3408         } else {
3409             return get_string('validateerror', 'admin');
3410         }
3411     }
3414 /**
3415  * Special checkbox for calendar - resets SESSION vars.
3416  */
3417 class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
3418     function admin_setting_special_adminseesall() {
3419         parent::admin_setting_configcheckbox('calendar_adminseesall', get_string('adminseesall', 'admin'),
3420                                              get_string('helpadminseesall', 'admin'), '0');
3421     }
3423     function write_setting($data) {
3424         global $SESSION;
3425         unset($SESSION->cal_courses_shown);
3426         return parent::write_setting($data);
3427     }
3430 /**
3431  * Special select for settings that are altered in setup.php and can not be altered on the fly
3432  */
3433 class admin_setting_special_selectsetup extends admin_setting_configselect {
3434     function get_setting() {
3435         // read directly from db!
3436         return get_config(NULL, $this->name);
3437     }
3439     function write_setting($data) {
3440         global $CFG;
3441         // do not change active CFG setting!
3442         $current = $CFG->{$this->name};
3443         $result = parent::write_setting($data);
3444         $CFG->{$this->name} = $current;
3445         return $result;
3446     }
3449 /**
3450  * Special select for frontpage - stores data in course table
3451  */
3452 class admin_setting_sitesetselect extends admin_setting_configselect {
3453     function get_setting() {
3454         $site = get_site();
3455         return $site->{$this->name};
3456     }
3458     function write_setting($data) {
3459         global $DB;
3460         if (!in_array($data, array_keys($this->choices))) {
3461             return get_string('errorsetting', 'admin');
3462         }
3463         $record = new stdClass();
3464         $record->id           = SITEID;
3465         $temp                 = $this->name;
3466         $record->$temp        = $data;
3467         $record->timemodified = time();
3468         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3469     }
3472 /**
3473  * Special select - lists on the frontpage - hacky
3474  */
3475 class admin_setting_courselist_frontpage extends admin_setting {
3476     var $choices;
3478     function admin_setting_courselist_frontpage($loggedin) {
3479         global $CFG;
3480         require_once($CFG->dirroot.'/course/lib.php');
3481         $name        = 'frontpage'.($loggedin ? 'loggedin' : '');
3482         $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
3483         $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
3484         $defaults    = array(FRONTPAGECOURSELIST);
3485         parent::admin_setting($name, $visiblename, $description, $defaults);
3486     }
3488     function load_choices() {
3489         global $DB;
3490         if (is_array($this->choices)) {
3491             return true;
3492         }
3493         $this->choices = array(FRONTPAGENEWS          => get_string('frontpagenews'),
3494                                FRONTPAGECOURSELIST    => get_string('frontpagecourselist'),
3495                                FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
3496                                FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
3497                                'none'                 => get_string('none'));
3498         if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
3499             unset($this->choices[FRONTPAGECOURSELIST]);
3500         }
3501         return true;
3502     }
3503     function get_setting() {
3504         $result = $this->config_read($this->name);
3505         if (is_null($result)) {
3506             return NULL;
3507         }
3508         if ($result === '') {
3509             return array();
3510         }
3511         return explode(',', $result);
3512     }
3514     function write_setting($data) {
3515         if (!is_array($data)) {
3516             return '';
3517         }
3518         $this->load_choices();
3519         $save = array();
3520         foreach($data as $datum) {
3521             if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
3522                 continue;
3523             }
3524             $save[$datum] = $datum; // no duplicates
3525         }
3526         return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3527     }
3529     function output_html($data, $query='') {
3530         $this->load_choices();
3531         $currentsetting = array();
3532         foreach ($data as $key) {
3533             if ($key != 'none' and array_key_exists($key, $this->choices)) {
3534                 $currentsetting[] = $key; // already selected first
3535             }
3536         }
3538         $return = '<div class="form-group">';
3539         for ($i = 0; $i < count($this->choices) - 1; $i++) {
3540             if (!array_key_exists($i, $currentsetting)) {
3541                 $currentsetting[$i] = 'none'; //none
3542             }
3543             $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
3544             foreach ($this->choices as $key => $value) {
3545                 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
3546             }
3547             $return .= '</select>';
3548             if ($i !== count($this->choices) - 2) {
3549                 $return .= '<br />';
3550             }
3551         }
3552         $return .= '</div>';
3554         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3555     }
3558 /**
3559  * Special checkbox for frontpage - stores data in course table
3560  */
3561 class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
3562     function get_setting() {
3563         $site = get_site();
3564         return $site->{$this->name};
3565     }
3567     function write_setting($data) {
3568         global $DB;
3569         $record = new object();
3570         $record->id            = SITEID;
3571         $record->{$this->name} = ($data == '1' ? 1 : 0);
3572         $record->timemodified  = time();
3573         return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3574     }
3577 /**
3578  * Special text for frontpage - stores data in course table.
3579  * Empty string means not set here. Manual setting is required.
3580  */
3581 class admin_setting_sitesettext extends admin_setting_configtext {
3582     function get_setting() {
3583         $site = get_site();
3584         return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
3585     }
3587     function validate($data) {
3588         $cleaned = clean_param($data, PARAM_MULTILANG);
3589         if ($cleaned === '') {
3590             return get_string('required');
3591         }
3592         if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
3593             return true;
3594         } else {
3595             return get_string('validateerror', 'admin');
3596         }
3597     }
3599     function write_setting($data) {
3600         global $DB;
3601         $data = trim($data);
3602         $validated = $this->validate($data);
3603         if ($validated !== true) {
3604             return $validated;
3605         }
3607         $record = new object();
3608         $record->id            = SITEID;
3609         $record->{$this->name} = $data;
3610         $record->timemodified  = time();
3611         return ($DB->update_record('course', $record) ? '' : get_string('dbupdatefailed', 'error'));
3612     }
3615 /**
3616  * Special text editor for site description.
3617  */
3618 class admin_setting_special_frontpagedesc extends admin_setting {
3619     function admin_setting_special_frontpagedesc() {
3620         parent::admin_setting('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
3621     }
3623     function get_setting() {
3624         $site = get_site();
3625         return $site->{$this->name};
3626     }
3628     function write_setting($data) {
3629         global $DB;
3630         $record = new object();
3631         $record->id            = SITEID;
3632         $record->{$this->name} = $data;
3633         $record->timemodified  = time();
3634         return($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
3635     }
3637     function output_html($data, $query='') {
3638         global $CFG;
3640         $CFG->adminusehtmleditor = can_use_html_editor();
3641         $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
3643         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3644     }
3647 class admin_setting_special_editorfontlist extends admin_setting {
3649     var $items;
3651     function admin_setting_special_editorfontlist() {
3652         global $CFG;
3653         $name = 'editorfontlist';
3654         $visiblename = get_string('editorfontlist', 'admin');
3655         $description = get_string('configeditorfontlist', 'admin');
3656         $defaults = array('k0' => 'Trebuchet',
3657                           'v0' => 'Trebuchet MS,Verdana,Arial,Helvetica,sans-serif',
3658                           'k1' => 'Arial',
3659                           'v1' => 'arial,helvetica,sans-serif',
3660                           'k2' => 'Courier New',
3661                           'v2' => 'courier new,courier,monospace',
3662                           'k3' => 'Georgia',
3663                           'v3' => 'georgia,times new roman,times,serif',
3664                           'k4' => 'Tahoma',
3665                           'v4' => 'tahoma,arial,helvetica,sans-serif',
3666                           'k5' => 'Times New Roman',
3667                           'v5' => 'times new roman,times,serif',
3668                           'k6' => 'Verdana',
3669                           'v6' => 'verdana,arial,helvetica,sans-serif',
3670                           'k7' => 'Impact',
3671                           'v7' => 'impact',
3672                           'k8' => 'Wingdings',
3673                           'v8' => 'wingdings');
3674         parent::admin_setting($name, $visiblename, $description, $defaults);
3675     }
3677     function get_setting() {
3678         global $CFG;
3679         $result = $this->config_read($this->name);
3680         if (is_null($result)) {
3681             return NULL;
3682         }
3683         $i = 0;
3684         $currentsetting = array();
3685         $items = explode(';', $result);
3686         foreach ($items as $item) {
3687           $item = explode(':', $item);
3688           $currentsetting['k'.$i] = $item[0];
3689           $currentsetting['v'.$i] = $item[1];
3690           $i++;
3691         }
3692         return $currentsetting;
3693     }
3695     function write_setting($data) {
3697         // there miiight be an easier way to do this :)
3698         // if this is changed, make sure the $defaults array above is modified so that this
3699         // function processes it correctly
3701         $keys = array();
3702         $values = array();
3704         foreach ($data as $key => $value) {
3705             if (substr($key,0,1) == 'k') {
3706                 $keys[substr($key,1)] = $value;
3707             } elseif (substr($key,0,1) == 'v') {
3708                 $values[substr($key,1)] = $value;
3709             }
3710         }
3712         $result = array();
3713         for ($i = 0; $i < count($keys); $i++) {
3714             if (($keys[$i] !== '') && ($values[$i] !== '')) {
3715                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).':'.clean_param($values[$i], PARAM_NOTAGS);
3716             }
3717         }
3719         return ($this->config_write($this->name, implode(';', $result)) ? '' : get_string('errorsetting', 'admin'));
3720     }
3722     function output_html($data, $query='') {
3723         $fullname = $this->get_full_name();
3724         $return = '<div class="form-group">';
3725         for ($i = 0; $i < count($data) / 2; $i++) {
3726             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
3727             $return .= '&nbsp;&nbsp;';
3728             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
3729         }
3730         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
3731         $return .= '&nbsp;&nbsp;';
3732         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3733         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
3734         $return .= '&nbsp;&nbsp;';
3735         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
3736         $return .= '</div>';
3738         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3739     }
3743 class admin_setting_emoticons extends admin_setting {
3745     var $items;
3747     function admin_setting_emoticons() {
3748         global $CFG;
3749         $name = 'emoticons';
3750         $visiblename = get_string('emoticons', 'admin');
3751         $description = get_string('configemoticons', 'admin');
3752         $defaults = array('k0' => ':-)',
3753                           'v0' => 'smiley',
3754                           'k1' => ':)',
3755                           'v1' => 'smiley',
3756                           'k2' => ':-D',
3757                           'v2' => 'biggrin',
3758                           'k3' => ';-)',
3759                           'v3' => 'wink',
3760                           'k4' => ':-/',
3761                           'v4' => 'mixed',
3762                           'k5' => 'V-.',
3763                           'v5' => 'thoughtful',
3764                           'k6' => ':-P',
3765                           'v6' => 'tongueout',
3766                           'k7' => 'B-)',
3767                           'v7' => 'cool',
3768                           'k8' => '^-)',
3769                           'v8' => 'approve',
3770                           'k9' => '8-)',
3771                           'v9' => 'wideeyes',
3772                           'k10' => ':o)',
3773                           'v10' => 'clown',
3774                           'k11' => ':-(',
3775                           'v11' => 'sad',
3776                           'k12' => ':(',
3777                           'v12' => 'sad',
3778                           'k13' => '8-.',
3779                           'v13' => 'shy',
3780                           'k14' => ':-I',
3781                           'v14' => 'blush',
3782                           'k15' => ':-X',
3783                           'v15' => 'kiss',
3784                           'k16' => '8-o',
3785                           'v16' => 'surprise',
3786                           'k17' => 'P-|',
3787                           'v17' => 'blackeye',
3788                           'k18' => '8-[',
3789                           'v18' => 'angry',
3790                           'k19' => 'xx-P',
3791                           'v19' => 'dead',
3792                           'k20' => '|-.',
3793                           'v20' => 'sleepy',
3794                           'k21' => '}-]',
3795                           'v21' => 'evil',
3796                           'k22' => '(h)',
3797                           'v22' => 'heart',
3798                           'k23' => '(heart)',
3799                           'v23' => 'heart',
3800                           'k24' => '(y)',
3801                           'v24' => 'yes',
3802                           'k25' => '(n)',
3803                           'v25' => 'no',
3804                           'k26' => '(martin)',
3805                           'v26' => 'martin',
3806                           'k27' => '( )',
3807                           'v27' => 'egg');
3808         parent::admin_setting($name, $visiblename, $description, $defaults);
3809     }
3811     function get_setting() {
3812         global $CFG;
3813         $result = $this->config_read($this->name);
3814         if (is_null($result)) {
3815             return NULL;
3816         }
3817         $i = 0;
3818         $currentsetting = array();
3819         $items = explode('{;}', $result);
3820         foreach ($items as $item) {
3821           $item = explode('{:}', $item);
3822           $currentsetting['k'.$i] = $item[0];
3823           $currentsetting['v'.$i] = $item[1];
3824           $i++;
3825         }
3826         return $currentsetting;
3827     }
3829     function write_setting($data) {
3831         // there miiight be an easier way to do this :)
3832         // if this is changed, make sure the $defaults array above is modified so that this
3833         // function processes it correctly
3835         $keys = array();
3836         $values = array();
3838         foreach ($data as $key => $value) {
3839             if (substr($key,0,1) == 'k') {
3840                 $keys[substr($key,1)] = $value;
3841             } elseif (substr($key,0,1) == 'v') {
3842                 $values[substr($key,1)] = $value;
3843             }
3844         }
3846         $result = array();
3847         for ($i = 0; $i < count($keys); $i++) {
3848             if (($keys[$i] !== '') && ($values[$i] !== '')) {
3849                 $result[] = clean_param($keys[$i],PARAM_NOTAGS).'{:}'.clean_param($values[$i], PARAM_NOTAGS);
3850             }
3851         }
3853         return ($this->config_write($this->name, implode('{;}', $result)) ? '' : get_string('errorsetting', 'admin').$this->visiblename.'<br />');
3854     }
3856     function output_html($data, $query='') {
3857         $fullname = $this->get_full_name();
3858         $return = '<div class="form-group">';
3859         for ($i = 0; $i < count($data) / 2; $i++) {
3860             $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
3861             $return .= '&nbsp;&nbsp;';
3862             $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
3863         }
3864         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
3865         $return .= '&nbsp;&nbsp;';
3866         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3867         $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
3868         $return .= '&nbsp;&nbsp;';
3869         $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
3870         $return .= '</div>';
3872         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3873     }
3877 class admin_setting_special_editorhidebuttons extends admin_setting {
3878     var $items;
3880     function admin_setting_special_editorhidebuttons() {
3881         parent::admin_setting('editorhidebuttons', get_string('editorhidebuttons', 'admin'),
3882                               get_string('confeditorhidebuttons', 'admin'), array());
3883         // weird array... buttonname => buttonimage (assume proper path appended). if you leave buttomimage blank, text will be printed instead
3884         $this->items = array('fontname' => '',
3885                          'fontsize' => '',
3886                          'formatblock' => '',
3887                          'bold' => 'ed_format_bold.gif',
3888                          'italic' => 'ed_format_italic.gif',
3889                          'underline' => 'ed_format_underline.gif',
3890                          'strikethrough' => 'ed_format_strike.gif',
3891                          'subscript' => 'ed_format_sub.gif',
3892                          'superscript' => 'ed_format_sup.gif',
3893                          'copy' => 'ed_copy.gif',
3894                          'cut' => 'ed_cut.gif',
3895                          'paste' => 'ed_paste.gif',
3896                          'clean' => 'ed_wordclean.gif',
3897                          'undo' => 'ed_undo.gif',
3898                          'redo' => 'ed_redo.gif',
3899                          'justifyleft' => 'ed_align_left.gif',
3900                          'justifycenter' => 'ed_align_center.gif',
3901                          'justifyright' => 'ed_align_right.gif',
3902                          'justifyfull' => 'ed_align_justify.gif',
3903                          'lefttoright' => 'ed_left_to_right.gif',
3904                          'righttoleft' => 'ed_right_to_left.gif',
3905                          'insertorderedlist' => 'ed_list_num.gif',
3906                          'insertunorderedlist' => 'ed_list_bullet.gif',
3907                          'outdent' => 'ed_indent_less.gif',
3908                          'indent' => 'ed_indent_more.gif',
3909                          'forecolor' => 'ed_color_fg.gif',
3910                          'hilitecolor' => 'ed_color_bg.gif',
3911                          'inserthorizontalrule' => 'ed_hr.gif',
3912                          'createanchor' => 'ed_anchor.gif',
3913                          'createlink' => 'ed_link.gif',
3914                          'unlink' => 'ed_unlink.gif',
3915                          'insertimage' => 'ed_image.gif',
3916                          'inserttable' => 'insert_table.gif',
3917                          'insertsmile' => 'em.icon.smile.gif',
3918                          'insertchar' => 'icon_ins_char.gif',
3919                          'spellcheck' => 'spell-check.gif',
3920                          'htmlmode' => 'ed_html.gif',
3921                          'popupeditor' => 'fullscreen_maximize.gif',
3922                          'search_replace' => 'ed_replace.gif');
3923     }
3925     function get_setting() {
3926         $result = $this->config_read($this->name);
3927         if (is_null($result)) {
3928             return NULL;
3929         }
3930         if ($result === '') {
3931             return array();
3932         }
3933         return explode(' ', $result);
3934     }
3936     function write_setting($data) {
3937         if (!is_array($data)) {
3938             return ''; // ignore it
3939         }
3940         unset($data['xxxxx']);
3941         $result = array();
3943         foreach ($data as $key => $value) {
3944             if (!isset($this->items[$key])) {
3945                 return get_string('errorsetting', 'admin');
3946             }
3947             if ($value == '1') {
3948                 $result[] = $key;
3949             }
3950         }
3951         return ($this->config_write($this->name, implode(' ', $result)) ? '' : get_string('errorsetting', 'admin'));
3952     }
3954     function output_html($data, $query='') {
3956         global $CFG;
3958         // checkboxes with input name="$this->name[$key]" value="1"
3959         // we do 15 fields per column
3961         $return = '<div class="form-group">';
3962         $return .= '<table><tr><td valign="top" align="right">';
3963         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
3965         $count = 0;
3967         foreach($this->items as $key => $value) {
3968             if ($count % 15 == 0 and $count != 0) {
3969                 $return .= '</td><td valign="top" align="right">';
3970             }
3972             $return .= '<label for="'.$this->get_id().$key.'">';
3973             $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;';
3974             $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;';
3975             $return .= '</label>';
3976             $count++;
3977             if ($count % 15 != 0) {
3978                 $return .= '<br /><br />';
3979             }
3980         }
3982         $return .= '</td></tr>';
3983         $return .= '</table>';
3984         $return .= '</div>';
3986         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
3987     }
3990 /**
3991  * Special setting for limiting of the list of available languages.
3992  */
3993 class admin_setting_langlist extends admin_setting_configtext {
3994     function admin_setting_langlist() {
3995         parent::admin_setting_configtext('langlist', get_string('langlist', 'admin'), get_string('configlanglist', 'admin'), '', PARAM_NOTAGS);
3996     }
3998     function write_setting($data) {
3999         $return = parent::write_setting($data);
4000         get_list_of_languages(true);//refresh the list
4001         return $return;
4002     }
4005 /**
4006  * Course category selection
4007  */
4008 class admin_settings_coursecat_select extends admin_setting_configselect {
4009     function admin_settings_coursecat_select($name, $visiblename, $description, $defaultsetting) {
4010         parent::admin_setting_configselect($name, $visiblename, $description, $defaultsetting, NULL);
4011     }
4013     function load_choices() {
4014         global $CFG;
4015         require_once($CFG->dirroot.'/course/lib.php');
4016         if (is_array($this->choices)) {
4017             return true;
4018         }
4019         $this->choices = make_categories_options();
4020         return true;
4021     }
4024 class admin_setting_special_backupdays extends admin_setting_configmulticheckbox2 {
4025     function admin_setting_special_backupdays() {
4026         parent::admin_setting_configmulticheckbox2('backup_sche_weekdays', get_string('schedule'), get_string('backupschedulehelp'), array(), NULL);
4027         $this->plugin = 'backup';
4028     }
4030     function load_choices() {
4031         if (is_array($this->choices)) {
4032             return true;
4033         }
4034         $this->choices = array();
4035         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
4036         foreach ($days as $day) {
4037             $this->choices[$day] = get_string($day, 'calendar');
4038         }
4039         return true;
4040     }
4043 /**
4044  * Special debug setting
4045  */
4046 class admin_setting_special_debug extends admin_setting_configselect {
4047     function admin_setting_special_debug() {
4048         parent::admin_setting_configselect('debug', get_string('debug', 'admin'), get_string('configdebug', 'admin'), DEBUG_NONE, NULL);
4049     }
4051     function load_choices() {
4052         if (is_array($this->choices)) {
4053             return true;
4054         }
4055         $this->choices = array(DEBUG_NONE      => get_string('debugnone', 'admin'),
4056                                DEBUG_MINIMAL   => get_string('debugminimal', 'admin'),
4057                                DEBUG_NORMAL    => get_string('debugnormal', 'admin'),
4058                                DEBUG_ALL       => get_string('debugall', 'admin'),
4059                                DEBUG_DEVELOPER => get_string('debugdeveloper', 'admin'));
4060         return true;
4061     }
4065 class admin_setting_special_calendar_weekend extends admin_setting {
4066     function admin_setting_special_calendar_weekend() {
4067         $name = 'calendar_weekend';
4068         $visiblename = get_string('calendar_weekend', 'admin');
4069         $description = get_string('helpweekenddays', 'admin');
4070         $default = array ('0', '6'); // Saturdays and Sundays
4071         parent::admin_setting($name, $visiblename, $description, $default);
4072     }
4074     function get_setting() {
4075         $result = $this->config_read($this->name);
4076         if (is_null($result)) {
4077             return NULL;
4078         }
4079         if ($result === '') {
4080             return array();
4081         }
4082         $settings = array();
4083         for ($i=0; $i<7; $i++) {
4084             if ($result & (1 << $i)) {
4085                 $settings[] = $i;
4086             }
4087         }
4088         return $settings;
4089     }
4091     function write_setting($data) {
4092         if (!is_array($data)) {
4093             return '';
4094         }
4095         unset($data['xxxxx']);
4096         $result = 0;
4097         foreach($data as $index) {
4098             $result |= 1 << $index;
4099         }
4100         return ($this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'));
4101     }
4103     function output_html($data, $query='') {
4104         // The order matters very much because of the implied numeric keys
4105         $days = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
4106         $return = '<table><thead><tr>';
4107         $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
4108         foreach($days as $index => $day) {
4109             $return .= '<td><label for="'.$this->get_id().$index.'">'.get_string($day, 'calendar').'</label></td>';
4110         }
4111         $return .= '</tr></thead><tbody><tr>';
4112         foreach($days as $index => $day) {
4113             $return .= '<td><input type="checkbox" class="form-checkbox" id="'.$this->get_id().$index.'" name="'.$this->get_full_name().'[]" value="'.$index.'" '.(in_array("$index", $data) ? 'checked="checked"' : '').' /></td>';
4114         }
4115         $return .= '</tr></tbody></table>';
4117         return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
4119     }
4123 /**
4124  * Admin setting that allows a user to pick appropriate roles for something.
4125  */
4126 class admin_setting_pickroles extends admin_setting_configmulticheckbox {
4127     private $types;
4129     /**
4130      * @param string $name Name of config variable
4131      * @param string $visiblename Display name
4132      * @param string $description Description
4133      * @param array $types Array of capabilities (usually moodle/legacy:something)
4134      *   which identify roles that will be enabled by default. Default is the
4135      *   student role
4136      */
4137     function admin_setting_pickroles($name, $visiblename, $description, $types) {
4138         parent::admin_setting_configmulticheckbox($name, $visiblename, $description, NULL, NULL);
4139         $this->types = $types;
4140     }
4142     function load_choices() {
4143         global $CFG, $DB;
4144         if (empty($CFG->rolesactive)) {
4145             return false;
4146         }
4147         if (is_array($this->choices)) {
4148             return true;
4149         }
4150         if ($roles = get_all_roles()) {
4151             $this->choices = array();
4152             foreach($roles as $role) {
4153                 $this->choices[$role->id] = format_string($role->name);
4154             }
4155             return true;
4156         } else {
4157             return false;
4158         }
4159     }
4161     function get_defaultsetting() {
4162         global $CFG;
4164         if (empty($CFG->rolesactive)) {
4165             return null;
4166         }
4167         $result = array();
4168         foreach($this->types as $capability) {
4169             if ($caproles = get_roles_with_capability($capability, CAP_ALLOW)) {
4170                 foreach ($caproles as $caprole) {
4171                     $result[$caprole->id] = 1;
4172                 }
4173             }
4174         }
4175         return $result;
4176     }