| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php 2 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: adminlib.php,v 1.153.2.54 2008/08/31 20:44:16 skodak Exp $ 8 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 9 * @package moodlecore 10 */ 11 12 define('INSECURE_DATAROOT_WARNING', 1); 13 define('INSECURE_DATAROOT_ERROR', 2); 14 15 function upgrade_main_savepoint($result, $version) { 16 global $CFG; 17 18 if ($result) { 19 if ($CFG->version >= $version) { 20 // something really wrong is going on in main upgrade script 21 error("Upgrade savepoint: Can not upgrade main version from $CFG->version to $version."); 22 } 23 set_config('version', $version); 24 } else { 25 notify ("Upgrade savepoint: Error during main upgrade to version $version"); 26 } 27 } 28 29 function upgrade_mod_savepoint($result, $version, $type) { 30 //TODO 31 } 32 33 function upgrade_plugin_savepoint($result, $version, $type, $dir) { 34 //TODO 35 } 36 37 function upgrade_backup_savepoint($result, $version) { 38 //TODO 39 } 40 41 function upgrade_blocks_savepoint($result, $version, $type) { 42 //TODO 43 } 44 45 /** 46 * Upgrade plugins 47 * 48 * @uses $db 49 * @uses $CFG 50 * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype') 51 * @param string $dir The directory where the plugins are located (e.g. 'question/questiontypes') 52 * @param string $return The url to prompt the user to continue to 53 */ 54 function upgrade_plugins($type, $dir, $return) { 55 global $CFG, $db; 56 57 /// Let's know if the header has been printed, so the funcion is being called embedded in an outer page 58 $embedded = defined('HEADER_PRINTED'); 59 60 $plugs = get_list_of_plugins($dir); 61 $updated_plugins = false; 62 $strpluginsetup = get_string('pluginsetup'); 63 64 foreach ($plugs as $plug) { 65 66 $fullplug = $CFG->dirroot .'/'.$dir.'/'. $plug; 67 68 unset($plugin); 69 70 if (is_readable($fullplug .'/version.php')) { 71 include_once ($fullplug .'/version.php'); // defines $plugin with version etc 72 } else { 73 continue; // Nothing to do. 74 } 75 76 $oldupgrade = false; 77 $newupgrade = false; 78 if (is_readable($fullplug . '/db/'. $CFG->dbtype . '.php')) { 79 include_once($fullplug . '/db/'. $CFG->dbtype . '.php'); // defines old upgrading function 80 $oldupgrade = true; 81 } 82 if (is_readable($fullplug . '/db/upgrade.php')) { 83 include_once ($fullplug . '/db/upgrade.php'); // defines new upgrading function 84 $newupgrade = true; 85 } 86 87 if (!isset($plugin)) { 88 continue; 89 } 90 91 if (!empty($plugin->requires)) { 92 if ($plugin->requires > $CFG->version) { 93 $info = new object(); 94 $info->pluginname = $plug; 95 $info->pluginversion = $plugin->version; 96 $info->currentmoodle = $CFG->version; 97 $info->requiremoodle = $plugin->requires; 98 if (!$updated_plugins && !$embedded) { 99 print_header($strpluginsetup, $strpluginsetup, 100 build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '', 101 upgrade_get_javascript(), false, ' ', ' '); 102 } 103 upgrade_log_start(); 104 notify(get_string('pluginrequirementsnotmet', 'error', $info)); 105 $updated_plugins = true; 106 continue; 107 } 108 } 109 110 $plugin->name = $plug; // The name MUST match the directory 111 112 $pluginversion = $type.'_'.$plug.'_version'; 113 114 if (!isset($CFG->$pluginversion)) { 115 set_config($pluginversion, 0); 116 } 117 118 if ($CFG->$pluginversion == $plugin->version) { 119 // do nothing 120 } else if ($CFG->$pluginversion < $plugin->version) { 121 if (!$updated_plugins && !$embedded) { 122 print_header($strpluginsetup, $strpluginsetup, 123 build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '', 124 upgrade_get_javascript(), false, ' ', ' '); 125 } 126 $updated_plugins = true; 127 upgrade_log_start(); 128 print_heading($dir.'/'. $plugin->name .' plugin needs upgrading'); 129 $db->debug = true; 130 @set_time_limit(0); // To allow slow databases to complete the long SQL 131 132 if ($CFG->$pluginversion == 0) { // It's a new install of this plugin 133 /// Both old .sql files and new install.xml are supported 134 /// but we priorize install.xml (XMLDB) if present 135 $status = false; 136 if (file_exists($fullplug . '/db/install.xml')) { 137 $status = install_from_xmldb_file($fullplug . '/db/install.xml'); //New method 138 } else if (file_exists($fullplug .'/db/'. $CFG->dbtype .'.sql')) { 139 $status = modify_database($fullplug .'/db/'. $CFG->dbtype .'.sql'); //Old method 140 } else { 141 $status = true; 142 } 143 144 $db->debug = false; 145 /// Continue with the instalation, roles and other stuff 146 if ($status) { 147 /// OK so far, now update the plugins record 148 set_config($pluginversion, $plugin->version); 149 150 /// Install capabilities 151 if (!update_capabilities($type.'/'.$plug)) { 152 error('Could not set up the capabilities for '.$plugin->name.'!'); 153 } 154 /// Install events 155 events_update_definition($type.'/'.$plug); 156 157 /// Run local install function if there is one 158 if (is_readable($fullplug .'/lib.php')) { 159 include_once($fullplug .'/lib.php'); 160 $installfunction = $plugin->name.'_install'; 161 if (function_exists($installfunction)) { 162 if (! $installfunction() ) { 163 notify('Encountered a problem running install function for '.$module->name.'!'); 164 } 165 } 166 } 167 168 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess'); 169 } else { 170 notify('Installing '. $plugin->name .' FAILED!'); 171 } 172 } else { // Upgrade existing install 173 /// Run de old and new upgrade functions for the module 174 $oldupgrade_function = $type.'_'.$plugin->name .'_upgrade'; 175 $newupgrade_function = 'xmldb_' . $type.'_'.$plugin->name .'_upgrade'; 176 177 /// First, the old function if exists 178 $oldupgrade_status = true; 179 if ($oldupgrade && function_exists($oldupgrade_function)) { 180 $db->debug = true; 181 $oldupgrade_status = $oldupgrade_function($CFG->$pluginversion); 182 } else if ($oldupgrade) { 183 notify ('Upgrade function ' . $oldupgrade_function . ' was not available in ' . 184 $fullplug . '/db/' . $CFG->dbtype . '.php'); 185 } 186 187 /// Then, the new function if exists and the old one was ok 188 $newupgrade_status = true; 189 if ($newupgrade && function_exists($newupgrade_function) && $oldupgrade_status) { 190 $db->debug = true; 191 $newupgrade_status = $newupgrade_function($CFG->$pluginversion); 192 } else if ($newupgrade) { 193 notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' . 194 $fullplug . '/db/upgrade.php'); 195 } 196 197 $db->debug=false; 198 /// Now analyze upgrade results 199 if ($oldupgrade_status && $newupgrade_status) { // No upgrading failed 200 // OK so far, now update the plugins record 201 set_config($pluginversion, $plugin->version); 202 if (!update_capabilities($type.'/'.$plug)) { 203 error('Could not update '.$plugin->name.' capabilities!'); 204 } 205 events_update_definition($type.'/'.$plug); 206 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess'); 207 } else { 208 notify('Upgrading '. $plugin->name .' from '. $CFG->$pluginversion .' to '. $plugin->version .' FAILED!'); 209 } 210 } 211 echo '<hr />'; 212 } else { 213 upgrade_log_start(); 214 error('Version mismatch: '. $plugin->name .' can\'t downgrade '. $CFG->$pluginversion .' -> '. $plugin->version .' !'); 215 } 216 } 217 218 upgrade_log_finish(); 219 220 if ($updated_plugins && !$embedded) { 221 print_continue($return); 222 print_footer('none'); 223 die; 224 } 225 } 226 227 /** 228 * Find and check all modules and load them up or upgrade them if necessary 229 * 230 * @uses $db 231 * @uses $CFG 232 * @param string $return The url to prompt the user to continue to 233 * @todo Finish documenting this function 234 */ 235 function upgrade_activity_modules($return) { 236 237 global $CFG, $db; 238 239 if (!$mods = get_list_of_plugins('mod') ) { 240 error('No modules installed!'); 241 } 242 243 $updated_modules = false; 244 $strmodulesetup = get_string('modulesetup'); 245 246 foreach ($mods as $mod) { 247 248 if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it 249 continue; 250 } 251 252 $fullmod = $CFG->dirroot .'/mod/'. $mod; 253 254 unset($module); 255 256 if ( is_readable($fullmod .'/version.php')) { 257 include_once ($fullmod .'/version.php'); // defines $module with version etc 258 } else { 259 notify('Module '. $mod .': '. $fullmod .'/version.php was not readable'); 260 continue; 261 } 262 263 $oldupgrade = false; 264 $newupgrade = false; 265 if ( is_readable($fullmod .'/db/' . $CFG->dbtype . '.php')) { 266 include_once($fullmod .'/db/' . $CFG->dbtype . '.php'); // defines old upgrading function 267 $oldupgrade = true; 268 } 269 if ( is_readable($fullmod . '/db/upgrade.php')) { 270 include_once ($fullmod . '/db/upgrade.php'); // defines new upgrading function 271 $newupgrade = true; 272 } 273 274 if (!isset($module)) { 275 continue; 276 } 277 278 if (!empty($module->requires)) { 279 if ($module->requires > $CFG->version) { 280 $info = new object(); 281 $info->modulename = $mod; 282 $info->moduleversion = $module->version; 283 $info->currentmoodle = $CFG->version; 284 $info->requiremoodle = $module->requires; 285 if (!$updated_modules) { 286 print_header($strmodulesetup, $strmodulesetup, 287 build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '', 288 upgrade_get_javascript(), false, ' ', ' '); 289 } 290 upgrade_log_start(); 291 notify(get_string('modulerequirementsnotmet', 'error', $info)); 292 $updated_modules = true; 293 continue; 294 } 295 } 296 297 $module->name = $mod; // The name MUST match the directory 298 299 include_once($fullmod.'/lib.php'); // defines upgrading and/or installing functions 300 301 if ($currmodule = get_record('modules', 'name', $module->name)) { 302 if ($currmodule->version == $module->version) { 303 // do nothing 304 } else if ($currmodule->version < $module->version) { 305 /// If versions say that we need to upgrade but no upgrade files are available, notify and continue 306 if (!$oldupgrade && !$newupgrade) { 307 notify('Upgrade files ' . $mod . ': ' . $fullmod . '/db/' . $CFG->dbtype . '.php or ' . 308 $fullmod . '/db/upgrade.php were not readable'); 309 continue; 310 } 311 if (!$updated_modules) { 312 print_header($strmodulesetup, $strmodulesetup, 313 build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '', 314 upgrade_get_javascript(), false, ' ', ' '); 315 } 316 upgrade_log_start(); 317 print_heading($module->name .' module needs upgrading'); 318 319 /// Run de old and new upgrade functions for the module 320 $oldupgrade_function = $module->name . '_upgrade'; 321 $newupgrade_function = 'xmldb_' . $module->name . '_upgrade'; 322 323 /// First, the old function if exists 324 $oldupgrade_status = true; 325 if ($oldupgrade && function_exists($oldupgrade_function)) { 326 $db->debug = true; 327 $oldupgrade_status = $oldupgrade_function($currmodule->version, $module); 328 if (!$oldupgrade_status) { 329 notify ('Upgrade function ' . $oldupgrade_function . 330 ' did not complete successfully.'); 331 } 332 } else if ($oldupgrade) { 333 notify ('Upgrade function ' . $oldupgrade_function . ' was not available in ' . 334 $mod . ': ' . $fullmod . '/db/' . $CFG->dbtype . '.php'); 335 } 336 337 /// Then, the new function if exists and the old one was ok 338 $newupgrade_status = true; 339 if ($newupgrade && function_exists($newupgrade_function) && $oldupgrade_status) { 340 $db->debug = true; 341 $newupgrade_status = $newupgrade_function($currmodule->version, $module); 342 } else if ($newupgrade && $oldupgrade_status) { 343 notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' . 344 $mod . ': ' . $fullmod . '/db/upgrade.php'); 345 } 346 347 $db->debug=false; 348 /// Now analyze upgrade results 349 if ($oldupgrade_status && $newupgrade_status) { // No upgrading failed 350 // OK so far, now update the modules record 351 $module->id = $currmodule->id; 352 if (! update_record('modules', $module)) { 353 error('Could not update '. $module->name .' record in modules table!'); 354 } 355 remove_dir($CFG->dataroot . '/cache', true); // flush cache 356 notify(get_string('modulesuccess', '', $module->name), 'notifysuccess'); 357 echo '<hr />'; 358 } else { 359 notify('Upgrading '. $module->name .' from '. $currmodule->version .' to '. $module->version .' FAILED!'); 360 } 361 362 /// Update the capabilities table? 363 if (!update_capabilities('mod/'.$module->name)) { 364 error('Could not update '.$module->name.' capabilities!'); 365 } 366 events_update_definition('mod/'.$module->name); 367 368 $updated_modules = true; 369 370 } else { 371 upgrade_log_start(); 372 error('Version mismatch: '. $module->name .' can\'t downgrade '. $currmodule->version .' -> '. $module->version .' !'); 373 } 374 375 } else { // module not installed yet, so install it 376 if (!$updated_modules) { 377 print_header($strmodulesetup, $strmodulesetup, 378 build_navigation(array(array('name' => $strmodulesetup, 'link' => null, 'type' => 'misc'))), '', 379 upgrade_get_javascript(), false, ' ', ' '); 380 } 381 upgrade_log_start(); 382 print_heading($module->name); 383 $updated_modules = true; 384 $db->debug = true; 385 @set_time_limit(0); // To allow slow databases to complete the long SQL 386 387 /// Both old .sql files and new install.xml are supported 388 /// but we priorize install.xml (XMLDB) if present 389 if (file_exists($fullmod . '/db/install.xml')) { 390 $status = install_from_xmldb_file($fullmod . '/db/install.xml'); //New method 391 } else { 392 $status = modify_database($fullmod .'/db/'. $CFG->dbtype .'.sql'); //Old method 393 } 394 395 $db->debug = false; 396 397 /// Continue with the installation, roles and other stuff 398 if ($status) { 399 if ($module->id = insert_record('modules', $module)) { 400 401 /// Capabilities 402 if (!update_capabilities('mod/'.$module->name)) { 403 error('Could not set up the capabilities for '.$module->name.'!'); 404 } 405 406 /// Events 407 events_update_definition('mod/'.$module->name); 408 409 /// Run local install function if there is one 410 $installfunction = $module->name.'_install'; 411 if (function_exists($installfunction)) { 412 if (! $installfunction() ) { 413 notify('Encountered a problem running install function for '.$module->name.'!'); 414 } 415 } 416 417 notify(get_string('modulesuccess', '', $module->name), 'notifysuccess'); 418 echo '<hr />'; 419 } else { 420 error($module->name .' module could not be added to the module list!'); 421 } 422 } else { 423 error($module->name .' tables could NOT be set up successfully!'); 424 } 425 } 426 427 /// Check submodules of this module if necessary 428 429 $submoduleupgrade = $module->name.'_upgrade_submodules'; 430 if (function_exists($submoduleupgrade)) { 431 $submoduleupgrade(); 432 } 433 434 435 /// Run any defaults or final code that is necessary for this module 436 437 if ( is_readable($fullmod .'/defaults.php')) { 438 // Insert default values for any important configuration variables 439 unset($defaults); 440 include($fullmod .'/defaults.php'); // include here means execute, not library include 441 if (!empty($defaults)) { 442 foreach ($defaults as $name => $value) { 443 if (!isset($CFG->$name)) { 444 set_config($name, $value); 445 } 446 } 447 } 448 } 449 } 450 451 upgrade_log_finish(); // finish logging if started 452 453 if ($updated_modules) { 454 print_continue($return); 455 print_footer('none'); 456 die; 457 } 458 } 459 460 /** 461 * Try to obtain or release the cron lock. 462 * 463 * @param string $name name of lock 464 * @param int $until timestamp when this lock considered stale, null means remove lock unconditionaly 465 * @param bool $ignorecurrent ignore current lock state, usually entend previous lock 466 * @return bool true if lock obtained 467 */ 468 function set_cron_lock($name, $until, $ignorecurrent=false) { 469 if (empty($name)) { 470 debugging("Tried to get a cron lock for a null fieldname"); 471 return false; 472 } 473 474 // remove lock by force == remove from config table 475 if (is_null($until)) { 476 set_config($name, null); 477 return true; 478 } 479 480 if (!$ignorecurrent) { 481 // read value from db - other processes might have changed it 482 $value = get_field('config', 'value', 'name', $name); 483 484 if ($value and $value > time()) { 485 //lock active 486 return false; 487 } 488 } 489 490 set_config($name, $until); 491 return true; 492 } 493 494 function print_progress($done, $total, $updatetime=5, $sleeptime=1, $donetext='') { 495 static $thisbarid; 496 static $starttime; 497 static $lasttime; 498 499 if ($total < 2) { // No need to show anything 500 return; 501 } 502 503 // Are we done? 504 if ($done >= $total) { 505 $done = $total; 506 if (!empty($thisbarid)) { 507 $donetext .= ' ('.$done.'/'.$total.') '.get_string('success'); 508 print_progress_redraw($thisbarid, $done, $total, 500, $donetext); 509 $thisbarid = $starttime = $lasttime = NULL; 510 } 511 return; 512 } 513 514 if (empty($starttime)) { 515 $starttime = $lasttime = time(); 516 $lasttime = $starttime - $updatetime; 517 $thisbarid = uniqid(); 518 echo '<table width="500" cellpadding="0" cellspacing="0" align="center"><tr><td width="500">'; 519 echo '<div id="bar'.$thisbarid.'" style="border-style:solid;border-width:1px;width:500px;height:50px;">'; 520 echo '<div id="slider'.$thisbarid.'" style="border-style:solid;border-width:1px;height:48px;width:10px;background-color:green;"></div>'; 521 echo '</div>'; 522 echo '<div id="text'.$thisbarid.'" align="center" style="width:500px;"></div>'; 523 echo '</td></tr></table>'; 524 echo '</div>'; 525 } 526 527 $now = time(); 528 529 if ($done && (($now - $lasttime) >= $updatetime)) { 530 $elapsedtime = $now - $starttime; 531 $projectedtime = (int)(((float)$total / (float)$done) * $elapsedtime) - $elapsedtime; 532 $percentage = round((float)$done / (float)$total, 2); 533 $width = (int)(500 * $percentage); 534 535 if ($projectedtime > 10) { 536 $projectedtext = ' Ending: '.format_time($projectedtime); 537 } else { 538 $projectedtext = ''; 539 } 540 541 $donetext .= ' ('.$done.'/'.$total.') '.$projectedtext; 542 print_progress_redraw($thisbarid, $done, $total, $width, $donetext); 543 544 $lasttime = $now; 545 } 546 } 547 548 // Don't call this function directly, it's called from print_progress. 549 function print_progress_redraw($thisbarid, $done, $total, $width, $donetext='') { 550 if (empty($thisbarid)) { 551 return; 552 } 553 echo '<script>'; 554 echo 'document.getElementById("text'.$thisbarid.'").innerHTML = "'.addslashes($donetext).'";'."\n"; 555 echo 'document.getElementById("slider'.$thisbarid.'").style.width = \''.$width.'px\';'."\n"; 556 echo '</script>'; 557 } 558 559 function upgrade_get_javascript() { 560 global $CFG; 561 562 if (!empty($_SESSION['installautopilot'])) { 563 $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = true;</script>'."\n"; 564 } else { 565 $linktoscrolltoerrors = '<script type="text/javascript">var installautopilot = false;</script>'."\n"; 566 } 567 $linktoscrolltoerrors .= '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>'; 568 569 return $linktoscrolltoerrors; 570 } 571 572 function create_admin_user() { 573 global $CFG, $USER; 574 575 if (empty($CFG->rolesactive)) { // No admin user yet. 576 577 $user = new object(); 578 $user->auth = 'manual'; 579 $user->firstname = get_string('admin'); 580 $user->lastname = get_string('user'); 581 $user->username = 'admin'; 582 $user->password = hash_internal_user_password('admin'); 583 $user->email = 'root@localhost'; 584 $user->confirmed = 1; 585 $user->mnethostid = $CFG->mnet_localhost_id; 586 $user->lang = $CFG->lang; 587 $user->maildisplay = 1; 588 $user->timemodified = time(); 589 590 if (!$user->id = insert_record('user', $user)) { 591 error('SERIOUS ERROR: Could not create admin user record !!!'); 592 } 593 594 if (!$user = get_record('user', 'id', $user->id)) { // Double check. 595 error('User ID was incorrect (can\'t find it)'); 596 } 597 598 // Assign the default admin roles to the new user. 599 if (!$adminroles = get_roles_with_capability('moodle/legacy:admin', CAP_ALLOW)) { 600 error('No admin role could be found'); 601 } 602 $sitecontext = get_context_instance(CONTEXT_SYSTEM); 603 foreach ($adminroles as $adminrole) { 604 role_assign($adminrole->id, $user->id, 0, $sitecontext->id); 605 } 606 607 set_config('rolesactive', 1); 608 609 // Log the user in. 610 $USER = get_complete_user_data('username', 'admin'); 611 $USER->newadminuser = 1; 612 load_all_capabilities(); 613 614 redirect("$CFG->wwwroot/user/editadvanced.php?id=$user->id"); // Edit thyself 615 } else { 616 error('Can not create admin!'); 617 } 618 } 619 620 //////////////////////////////////////////////// 621 /// upgrade logging functions 622 //////////////////////////////////////////////// 623 624 $upgradeloghandle = false; 625 $upgradelogbuffer = ''; 626 // I did not find out how to use static variable in callback function, 627 // the problem was that I could not flush the static buffer :-( 628 global $upgradeloghandle, $upgradelogbuffer; 629 630 /** 631 * Check if upgrade is already running. 632 * 633 * If anything goes wrong due to missing call to upgrade_log_finish() 634 * just restart the browser. 635 * 636 * @param string warning message indicating upgrade is already running 637 * @param int page reload timeout 638 */ 639 function upgrade_check_running($message, $timeout) { 640 if (!empty($_SESSION['upgraderunning'])) { 641 print_header(); 642 redirect(me(), $message, $timeout); 643 } 644 } 645 646 /** 647 * Start logging of output into file (if not disabled) and 648 * prevent aborting and concurrent execution of upgrade script. 649 * 650 * Please note that you can not write into session variables after calling this function! 651 * 652 * This function may be called repeatedly. 653 */ 654 function upgrade_log_start() { 655 global $CFG, $upgradeloghandle; 656 657 if (!empty($_SESSION['upgraderunning'])) { 658 return; // logging already started 659 } 660 661 @ignore_user_abort(true); // ignore if user stops or otherwise aborts page loading 662 $_SESSION['upgraderunning'] = 1; // set upgrade indicator 663 if (empty($CFG->dbsessions)) { // workaround for bug in adodb, db session can not be restarted 664 session_write_close(); // from now on user can reload page - will be displayed warning 665 } 666 make_upload_directory('upgradelogs'); 667 ob_start('upgrade_log_callback', 2); // function for logging to disk; flush each line of text ASAP 668 register_shutdown_function('upgrade_log_finish'); // in case somebody forgets to stop logging 669 } 670 671 /** 672 * Terminate logging of output, flush all data, allow script aborting 673 * and reopen session for writing. Function error() does terminate the logging too. 674 * 675 * Please make sure that each upgrade_log_start() is properly terminated by 676 * this function or error(). 677 * 678 * This function may be called repeatedly. 679 */ 680 function upgrade_log_finish() { 681 global $CFG, $upgradeloghandle, $upgradelogbuffer; 682 683 if (empty($_SESSION['upgraderunning'])) { 684 return; // logging already terminated 685 } 686 687 @ob_end_flush(); 688 if ($upgradelogbuffer !== '') { 689 @fwrite($upgradeloghandle, $upgradelogbuffer); 690 $upgradelogbuffer = ''; 691 } 692 if ($upgradeloghandle and ($upgradeloghandle !== 'error')) { 693 @fclose($upgradeloghandle); 694 $upgradeloghandle = false; 695 } 696 if (empty($CFG->dbsessions)) { 697 @session_start(); // ignore header errors, we only need to reopen session 698 } 699 $_SESSION['upgraderunning'] = 0; // clear upgrade indicator 700 if (connection_aborted()) { 701 die; 702 } 703 @ignore_user_abort(false); 704 } 705 706 /** 707 * Callback function for logging into files. Not more than one file is created per minute, 708 * upgrade session (terminated by upgrade_log_finish()) is always stored in one file. 709 * 710 * This function must not output any characters or throw warnigns and errors! 711 */ 712 function upgrade_log_callback($string) { 713 global $CFG, $upgradeloghandle, $upgradelogbuffer; 714 715 if (empty($CFG->disableupgradelogging) and ($string != '') and ($upgradeloghandle !== 'error')) { 716 if ($upgradeloghandle or ($upgradeloghandle = @fopen($CFG->dataroot.'/upgradelogs/upg_'.date('Ymd-Hi').'.html', 'a'))) { 717 $upgradelogbuffer .= $string; 718 if (strlen($upgradelogbuffer) > 2048) { // 2kB write buffer 719 @fwrite($upgradeloghandle, $upgradelogbuffer); 720 $upgradelogbuffer = ''; 721 } 722 } else { 723 $upgradeloghandle = 'error'; 724 } 725 } 726 return $string; 727 } 728 729 /** 730 * Test if and critical warnings are present 731 * @return bool 732 */ 733 function admin_critical_warnings_present() { 734 global $SESSION; 735 736 if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) { 737 return 0; 738 } 739 740 if (!isset($SESSION->admin_critical_warning)) { 741 $SESSION->admin_critical_warning = 0; 742 if (ini_get_bool('register_globals')) { 743 $SESSION->admin_critical_warning = 1; 744 } else if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) { 745 $SESSION->admin_critical_warning = 1; 746 } 747 } 748 749 return $SESSION->admin_critical_warning; 750 } 751 752 /** 753 * Try to verify that dataroot is not accessible from web. 754 * It is not 100% correct but might help to reduce number of vulnerable sites. 755 * 756 * Protection from httpd.conf and .htaccess is not detected properly. 757 * @param bool $fetchtest try to test public access by fetching file 758 * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING migth be problematic 759 */ 760 function is_dataroot_insecure($fetchtest=false) { 761 global $CFG; 762 763 $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround 764 765 $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1); 766 $rp = strrev(trim($rp, '/')); 767 $rp = explode('/', $rp); 768 foreach($rp as $r) { 769 if (strpos($siteroot, '/'.$r.'/') === 0) { 770 $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory 771 } else { 772 break; // probably alias root 773 } 774 } 775 776 $siteroot = strrev($siteroot); 777 $dataroot = str_replace('\\', '/', $CFG->dataroot.'/'); 778 779 if (strpos($dataroot, $siteroot) !== 0) { 780 return false; 781 } 782 783 if (!$fetchtest) { 784 return INSECURE_DATAROOT_WARNING; 785 } 786 787 // now try all methods to fetch a test file using http protocol 788 789 $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); 790 preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches); 791 $httpdocroot = $matches[1]; 792 $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot)); 793 if (make_upload_directory('diag', false) === false) { 794 return INSECURE_DATAROOT_WARNING; 795 } 796 $testfile = $CFG->dataroot.'/diag/public.txt'; 797 if (!file_exists($testfile)) { 798 file_put_contents($testfile, 'test file, do not delete'); 799 } 800 $teststr = trim(file_get_contents($testfile)); 801 if (empty($teststr)) { 802 // hmm, strange 803 return INSECURE_DATAROOT_WARNING; 804 } 805 806 $testurl = $datarooturl.'/diag/public.txt'; 807 808 if (extension_loaded('curl') and ($ch = @curl_init($testurl)) !== false) { 809 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 810 curl_setopt($ch, CURLOPT_HEADER, false); 811 $data = curl_exec($ch); 812 if (!curl_errno($ch)) { 813 $data = trim($data); 814 if ($data === $teststr) { 815 curl_close($ch); 816 return INSECURE_DATAROOT_ERROR; 817 } 818 } 819 curl_close($ch); 820 } 821 822 if ($data = @file_get_contents($testurl)) { 823 $data = trim($data); 824 if ($data === $teststr) { 825 return INSECURE_DATAROOT_ERROR; 826 } 827 } 828 829 preg_match('|https?://([^/]+)|i', $testurl, $matches); 830 $sitename = $matches[1]; 831 $error = 0; 832 if ($fp = @fsockopen($sitename, 80, $error)) { 833 preg_match('|https?://[^/]+(.*)|i', $testurl, $matches); 834 $localurl = $matches[1]; 835 $out = "GET $localurl HTTP/1.1\r\n"; 836 $out .= "Host: $sitename\r\n"; 837 $out .= "Connection: Close\r\n\r\n"; 838 fwrite($fp, $out); 839 $data = ''; 840 $incoming = false; 841 while (!feof($fp)) { 842 if ($incoming) { 843 $data .= fgets($fp, 1024); 844 } else if (@fgets($fp, 1024) === "\r\n") { 845 $incoming = true; 846 } 847 } 848 fclose($fp); 849 $data = trim($data); 850 if ($data === $teststr) { 851 return INSECURE_DATAROOT_ERROR; 852 } 853 } 854 855 return INSECURE_DATAROOT_WARNING; 856 } 857 858 /// ============================================================================================================= 859 /// administration tree classes and functions 860 861 862 // n.b. documentation is still in progress for this code 863 864 /// INTRODUCTION 865 866 /// This file performs the following tasks: 867 /// -it defines the necessary objects and interfaces to build the Moodle 868 /// admin hierarchy 869 /// -it defines the admin_externalpage_setup(), admin_externalpage_print_header(), 870 /// and admin_externalpage_print_footer() functions used on admin pages 871 872 /// ADMIN_SETTING OBJECTS 873 874 /// Moodle settings are represented by objects that inherit from the admin_setting 875 /// class. These objects encapsulate how to read a setting, how to write a new value 876 /// to a setting, and how to appropriately display the HTML to modify the setting. 877 878 /// ADMIN_SETTINGPAGE OBJECTS 879 880 /// The admin_setting objects are then grouped into admin_settingpages. The latter 881 /// appear in the Moodle admin tree block. All interaction with admin_settingpage 882 /// objects is handled by the admin/settings.php file. 883 884 /// ADMIN_EXTERNALPAGE OBJECTS 885 886 /// There are some settings in Moodle that are too complex to (efficiently) handle 887 /// with admin_settingpages. (Consider, for example, user management and displaying 888 /// lists of users.) In this case, we use the admin_externalpage object. This object 889 /// places a link to an external PHP file in the admin tree block. 890 891 /// If you're using an admin_externalpage object for some settings, you can take 892 /// advantage of the admin_externalpage_* functions. For example, suppose you wanted 893 /// to add a foo.php file into admin. First off, you add the following line to 894 /// admin/settings/first.php (at the end of the file) or to some other file in 895 /// admin/settings: 896 897 /// $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'), 898 /// $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission')); 899 900 /// Next, in foo.php, your file structure would resemble the following: 901 902 /// require_once('.../config.php'); 903 /// require_once($CFG->libdir.'/adminlib.php'); 904 /// admin_externalpage_setup('foo'); 905 /// // functionality like processing form submissions goes here 906 /// admin_externalpage_print_header(); 907 /// // your HTML goes here 908 /// admin_externalpage_print_footer(); 909 910 /// The admin_externalpage_setup() function call ensures the user is logged in, 911 /// and makes sure that they have the proper role permission to access the page. 912 913 /// The admin_externalpage_print_header() function prints the header (it figures 914 /// out what category and subcategories the page is classified under) and ensures 915 /// that you're using the admin pagelib (which provides the admin tree block and 916 /// the admin bookmarks block). 917 918 /// The admin_externalpage_print_footer() function properly closes the tables 919 /// opened up by the admin_externalpage_print_header() function and prints the 920 /// standard Moodle footer. 921 922 /// ADMIN_CATEGORY OBJECTS 923 924 /// Above and beyond all this, we have admin_category objects. These objects 925 /// appear as folders in the admin tree block. They contain admin_settingpage's, 926 /// admin_externalpage's, and other admin_category's. 927 928 /// OTHER NOTES 929 930 /// admin_settingpage's, admin_externalpage's, and admin_category's all inherit 931 /// from part_of_admin_tree (a pseudointerface). This interface insists that 932 /// a class has a check_access method for access permissions, a locate method 933 /// used to find a specific node in the admin tree and find parent path. 934 935 /// admin_category's inherit from parentable_part_of_admin_tree. This pseudo- 936 /// interface ensures that the class implements a recursive add function which 937 /// accepts a part_of_admin_tree object and searches for the proper place to 938 /// put it. parentable_part_of_admin_tree implies part_of_admin_tree. 939 940 /// Please note that the $this->name field of any part_of_admin_tree must be 941 /// UNIQUE throughout the ENTIRE admin tree. 942 943 /// The $this->name field of an admin_setting object (which is *not* part_of_ 944 /// admin_tree) must be unique on the respective admin_settingpage where it is 945 /// used. 946 947 948 /// CLASS DEFINITIONS ///////////////////////////////////////////////////////// 949 950 /** 951 * Pseudointerface for anything appearing in the admin tree 952 * 953 * The pseudointerface that is implemented by anything that appears in the admin tree 954 * block. It forces inheriting classes to define a method for checking user permissions 955 * and methods for finding something in the admin tree. 956 * 957 * @author Vincenzo K. Marcovecchio 958 * @package admin 959 */ 960 class part_of_admin_tree { 961 962 /** 963 * Finds a named part_of_admin_tree. 964 * 965 * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree 966 * and not parentable_part_of_admin_tree, then this function should only check if 967 * $this->name matches $name. If it does, it should return a reference to $this, 968 * otherwise, it should return a reference to NULL. 969 * 970 * If a class inherits parentable_part_of_admin_tree, this method should be called 971 * recursively on all child objects (assuming, of course, the parent object's name 972 * doesn't match the search criterion). 973 * 974 * @param string $name The internal name of the part_of_admin_tree we're searching for. 975 * @return mixed An object reference or a NULL reference. 976 */ 977 function &locate($name) { 978 trigger_error('Admin class does not implement method <strong>locate()</strong>', E_USER_WARNING); 979 return; 980 } 981 982 /** 983 * Removes named part_of_admin_tree. 984 * 985 * @param string $name The internal name of the part_of_admin_tree we want to remove. 986 * @return bool success. 987 */ 988 function prune($name) { 989 trigger_error('Admin class does not implement method <strong>prune()</strong>', E_USER_WARNING); 990 return; 991 } 992 993 /** 994 * Search using query 995 * @param strin query 996 * @return mixed array-object structure of found settings and pages 997 */ 998 function search($query) { 999 trigger_error('Admin class does not implement method <strong>search()</strong>', E_USER_WARNING); 1000 return; 1001 } 1002 1003 /** 1004 * Verifies current user's access to this part_of_admin_tree. 1005 * 1006 * Used to check if the current user has access to this part of the admin tree or 1007 * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree, 1008 * then this method is usually just a call to has_capability() in the site context. 1009 * 1010 * If a class inherits parentable_part_of_admin_tree, this method should return the 1011 * logical OR of the return of check_access() on all child objects. 1012 * 1013 * @return bool True if the user has access, false if she doesn't. 1014 */ 1015 function check_access() { 1016 trigger_error('Admin class does not implement method <strong>check_access()</strong>', E_USER_WARNING); 1017 return; 1018 } 1019 1020 /** 1021 * Mostly usefull for removing of some parts of the tree in admin tree block. 1022 * 1023 * @return True is hidden from normal list view 1024 */ 1025 function is_hidden() { 1026 trigger_error('Admin class does not implement method <strong>is_hidden()</strong>', E_USER_WARNING); 1027 return; 1028 } 1029 } 1030 1031 /** 1032 * Pseudointerface implemented by any part_of_admin_tree that has children. 1033 * 1034 * The pseudointerface implemented by any part_of_admin_tree that can be a parent 1035 * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart 1036 * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods 1037 * include an add method for adding other part_of_admin_tree objects as children. 1038 * 1039 * @author Vincenzo K. Marcovecchio 1040 * @package admin 1041 */ 1042 class parentable_part_of_admin_tree extends part_of_admin_tree { 1043 1044 /** 1045 * Adds a part_of_admin_tree object to the admin tree. 1046 * 1047 * Used to add a part_of_admin_tree object to this object or a child of this 1048 * object. $something should only be added if $destinationname matches 1049 * $this->name. If it doesn't, add should be called on child objects that are 1050 * also parentable_part_of_admin_tree's. 1051 * 1052 * @param string $destinationname The internal name of the new parent for $something. 1053 * @param part_of_admin_tree &$something The object to be added. 1054 * @return bool True on success, false on failure. 1055 */ 1056 function add($destinationname, $something) { 1057 trigger_error('Admin class does not implement method <strong>add()</strong>', E_USER_WARNING); 1058 return; 1059 } 1060 1061 } 1062 1063 /** 1064 * The object used to represent folders (a.k.a. categories) in the admin tree block. 1065 * 1066 * Each admin_category object contains a number of part_of_admin_tree objects. 1067 * 1068 * @author Vincenzo K. Marcovecchio 1069 * @package admin 1070 */ 1071 class admin_category extends parentable_part_of_admin_tree { 1072 1073 /** 1074 * @var mixed An array of part_of_admin_tree objects that are this object's children 1075 */ 1076 var $children; 1077 1078 /** 1079 * @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects 1080 */ 1081 var $name; 1082 1083 /** 1084 * @var string The displayed name for this category. Usually obtained through get_string() 1085 */ 1086 var $visiblename; 1087 1088 /** 1089 * @var bool Should this category be hidden in admin tree block? 1090 */ 1091 var $hidden; 1092 1093 /** 1094 * paths 1095 */ 1096 var $path; 1097 var $visiblepath; 1098 1099 /** 1100 * Constructor for an empty admin category 1101 * 1102 * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects 1103 * @param string $visiblename The displayed named for this category. Usually obtained through get_string() 1104 * @param bool $hidden hide category in admin tree block 1105 */ 1106 function admin_category($name, $visiblename, $hidden=false) { 1107 $this->children = array(); 1108 $this->name = $name; 1109 $this->visiblename = $visiblename; 1110 $this->hidden = $hidden; 1111 } 1112 1113 /** 1114 * Returns a reference to the part_of_admin_tree object with internal name $name. 1115 * 1116 * @param string $name The internal name of the object we want. 1117 * @param bool $findpath initialize path and visiblepath arrays 1118 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL. 1119 */ 1120 function &locate($name, $findpath=false) { 1121 if ($this->name == $name) { 1122 if ($findpath) { 1123 $this->visiblepath[] = $this->visiblename; 1124 $this->path[] = $this->name; 1125 } 1126 return $this; 1127 } 1128 1129 $return = NULL; 1130 foreach($this->children as $childid=>$unused) { 1131 if ($return =& $this->children[$childid]->locate($name, $findpath)) { 1132 break; 1133 } 1134 } 1135 1136 if (!is_null($return) and $findpath) { 1137 $return->visiblepath[] = $this->visiblename; 1138 $return->path[] = $this->name; 1139 } 1140 1141 return $return; 1142 } 1143 1144 /** 1145 * Search using query 1146 * @param strin query 1147 * @return mixed array-object structure of found settings and pages 1148 */ 1149 function search($query) { 1150 $result = array(); 1151 foreach ($this->children as $child) { 1152 $subsearch = $child->search($query); 1153 if (!is_array($subsearch)) { 1154 debugging('Incorrect search result from '.$child->name); 1155 continue; 1156 } 1157 $result = array_merge($result, $subsearch); 1158 } 1159 return $result; 1160 } 1161 1162 /** 1163 * Removes part_of_admin_tree object with internal name $name. 1164 * 1165 * @param string $name The internal name of the object we want to remove. 1166 * @return bool success 1167 */ 1168 function prune($name) { 1169 1170 if ($this->name == $name) { 1171 return false; //can not remove itself 1172 } 1173 1174 foreach($this->children as $precedence => $child) { 1175 if ($child->name == $name) { 1176 // found it! 1177 unset($this->children[$precedence]); 1178 return true; 1179 } 1180 if ($this->children[$precedence]->prune($name)) { 1181 return true; 1182 } 1183 } 1184 return false; 1185 } 1186 1187 /** 1188 * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object. 1189 * 1190 * @param string $destinationame The internal name of the immediate parent that we want for $something. 1191 * @param mixed $something A part_of_admin_tree or setting instanceto be added. 1192 * @return bool True if successfully added, false if $something can not be added. 1193 */ 1194 function add($parentname, $something) { 1195 $parent =& $this->locate($parentname); 1196 if (is_null($parent)) { 1197 debugging('parent does not exist!'); 1198 return false; 1199 } 1200 1201 if (is_a($something, 'part_of_admin_tree')) { 1202 if (!is_a($parent, 'parentable_part_of_admin_tree')) { 1203 debugging('error - parts of tree can be inserted only into parentable parts'); 1204 return false; 1205 } 1206 $parent->children[] = $something; 1207 return true; 1208 1209 } else { 1210 debugging('error - can not add this element'); 1211 return false; 1212 } 1213 1214 } 1215 1216 /** 1217 * Checks if the user has access to anything in this category. 1218 * 1219 * @return bool True if the user has access to atleast one child in this category, false otherwise. 1220 */ 1221 function check_access() { 1222 foreach ($this->children as $child) { 1223 if ($child->check_access()) { 1224 return true; 1225 } 1226 } 1227 return false; 1228 } 1229 1230 /** 1231 * Is this category hidden in admin tree block? 1232 * 1233 * @return bool True if hidden 1234 */ 1235 function is_hidden() { 1236 return $this->hidden; 1237 } 1238 } 1239 1240 class admin_root extends admin_category { 1241 /** 1242 * list of errors 1243 */ 1244 var $errors; 1245 1246 /** 1247 * search query 1248 */ 1249 var $search; 1250 1251 /** 1252 * full tree flag - true means all settings required, false onlypages required 1253 */ 1254 var $fulltree; 1255 1256 1257 function admin_root() { 1258 parent::admin_category('root', get_string('administration'), false); 1259 $this->errors = array(); 1260 $this->search = ''; 1261 $this->fulltree = true; 1262 } 1263 } 1264 1265 /** 1266 * Links external PHP pages into the admin tree. 1267 * 1268 * See detailed usage example at the top of this document (adminlib.php) 1269 * 1270 * @author Vincenzo K. Marcovecchio 1271 * @package admin 1272 */ 1273 class admin_externalpage extends part_of_admin_tree { 1274 1275 /** 1276 * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects 1277 */ 1278 var $name; 1279 1280 /** 1281 * @var string The displayed name for this external page. Usually obtained through get_string(). 1282 */ 1283 var $visiblename; 1284 1285 /** 1286 * @var string The external URL that we should link to when someone requests this external page. 1287 */ 1288 var $url; 1289 1290 /** 1291 * @var string The role capability/permission a user must have to access this external page. 1292 */ 1293 var $req_capability; 1294 1295 /** 1296 * @var object The context in which capability/permission should be checked, default is site context. 1297 */ 1298 var $context; 1299 1300 /** 1301 * @var bool hidden in admin tree block. 1302 */ 1303 var $hidden; 1304 1305 /** 1306 * visible path 1307 */ 1308 var $path; 1309 var $visiblepath; 1310 1311 /** 1312 * Constructor for adding an external page into the admin tree. 1313 * 1314 * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects. 1315 * @param string $visiblename The displayed name for this external page. Usually obtained through get_string(). 1316 * @param string $url The external URL that we should link to when someone requests this external page. 1317 * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'. 1318 */ 1319 function admin_externalpage($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) { 1320 $this->name = $name; 1321 $this->visiblename = $visiblename; 1322 $this->url = $url; 1323 if (is_array($req_capability)) { 1324 $this->req_capability = $req_capability; 1325 } else { 1326 $this->req_capability = array($req_capability); 1327 } 1328 $this->hidden = $hidden; 1329 $this->context = $context; 1330 } 1331 1332 /** 1333 * Returns a reference to the part_of_admin_tree object with internal name $name. 1334 * 1335 * @param string $name The internal name of the object we want. 1336 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL. 1337 */ 1338 function &locate($name, $findpath=false) { 1339 if ($this->name == $name) { 1340 if ($findpath) { 1341 $this->visiblepath = array($this->visiblename); 1342 $this->path = array($this->name); 1343 } 1344 return $this; 1345 } else { 1346 $return = NULL; 1347 return $return; 1348 } 1349 } 1350 1351 function prune($name) { 1352 return false; 1353 } 1354 1355 /** 1356 * Search using query 1357 * @param strin query 1358 * @return mixed array-object structure of found settings and pages 1359 */ 1360 function search($query) { 1361 $textlib = textlib_get_instance(); 1362 1363 $found = false; 1364 if (strpos(strtolower($this->name), $query) !== false) { 1365 $found = true; 1366 } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) { 1367 $found = true; 1368 } 1369 if ($found) { 1370 $result = new object(); 1371 $result->page = $this; 1372 $result->settings = array(); 1373 return array($this->name => $result); 1374 } else { 1375 return array(); 1376 } 1377 } 1378 1379 /** 1380 * Determines if the current user has access to this external page based on $this->req_capability. 1381 * @return bool True if user has access, false otherwise. 1382 */ 1383 function check_access() { 1384 if (!get_site()) { 1385 return true; // no access check before site is fully set up 1386 } 1387 $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context; 1388 foreach($this->req_capability as $cap) { 1389 if (has_capability($cap, $context)) { 1390 return true; 1391 } 1392 } 1393 return false; 1394 } 1395 1396 /** 1397 * Is this external page hidden in admin tree block? 1398 * 1399 * @return bool True if hidden 1400 */ 1401 function is_hidden() { 1402 return $this->hidden; 1403 } 1404 1405 } 1406 1407 /** 1408 * Used to group a number of admin_setting objects into a page and add them to the admin tree. 1409 * 1410 * @author Vincenzo K. Marcovecchio 1411 * @package admin 1412 */ 1413 class admin_settingpage extends part_of_admin_tree { 1414 1415 /** 1416 * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects 1417 */ 1418 var $name; 1419 1420 /** 1421 * @var string The displayed name for this external page. Usually obtained through get_string(). 1422 */ 1423 var $visiblename; 1424 /** 1425 * @var mixed An array of admin_setting objects that are part of this setting page. 1426 */ 1427 var $settings; 1428 1429 /** 1430 * @var string The role capability/permission a user must have to access this external page. 1431 */ 1432 var $req_capability; 1433 1434 /** 1435 * @var object The context in which capability/permission should be checked, default is site context. 1436 */ 1437 var $context; 1438 1439 /** 1440 * @var bool hidden in admin tree block. 1441 */ 1442 var $hidden; 1443 1444 /** 1445 * paths 1446 */ 1447 var $path; 1448 var $visiblepath; 1449 1450 // see admin_externalpage 1451 function admin_settingpage($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) { 1452 $this->settings = new object(); 1453 $this->name = $name; 1454 $this->visiblename = $visiblename; 1455 if (is_array($req_capability)) { 1456 $this->req_capability = $req_capability; 1457 } else { 1458 $this->req_capability = array($req_capability); 1459 } 1460 $this->hidden = $hidden; 1461 $this->context = $context; 1462 } 1463 1464 // see admin_category 1465 function &locate($name, $findpath=false) { 1466 if ($this->name == $name) { 1467 if ($findpath) { 1468 $this->visiblepath = array($this->visiblename); 1469 $this->path = array($this->name); 1470 } 1471 return $this; 1472 } else { 1473 $return = NULL; 1474 return $return; 1475 } 1476 } 1477 1478 function search($query) { 1479 $found = array(); 1480 1481 foreach ($this->settings as $setting) { 1482 if ($setting->is_related($query)) { 1483 $found[] = $setting; 1484 } 1485 } 1486 1487 if ($found) { 1488 $result = new object(); 1489 $result->page = $this; 1490 $result->settings = $found; 1491 return array($this->name => $result); 1492 } 1493 1494 $textlib = textlib_get_instance(); 1495 1496 $found = false; 1497 if (strpos(strtolower($this->name), $query) !== false) { 1498 $found = true; 1499 } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) { 1500 $found = true; 1501 } 1502 if ($found) { 1503 $result = new object(); 1504 $result->page = $this; 1505 $result->settings = array(); 1506 return array($this->name => $result); 1507 } else { 1508 return array(); 1509 } 1510 } 1511 1512 function prune($name) { 1513 return false; 1514 } 1515 1516 /** 1517 * 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 1518 * n.b. each admin_setting in an admin_settingpage must have a unique internal name 1519 * @param object $setting is the admin_setting object you want to add 1520 * @return true if successful, false if not 1521 */ 1522 function add($setting) { 1523 if (!is_a($setting, 'admin_setting')) { 1524 debugging('error - not a setting instance'); 1525 return false; 1526 } 1527 1528 $this->settings->{$setting->name} = $setting; 1529 return true; 1530 } 1531 1532 // see admin_externalpage 1533 function check_access() { 1534 if (!get_site()) { 1535 return true; // no access check before site is fully set up 1536 } 1537 $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context; 1538 foreach($this->req_capability as $cap) { 1539 if (has_capability($cap, $context)) { 1540 return true; 1541 } 1542 } 1543 return false; 1544 } 1545 1546 /** 1547 * outputs this page as html in a table (suitable for inclusion in an admin pagetype) 1548 * returns a string of the html 1549 */ 1550 function output_html() { 1551 $adminroot =& admin_get_root(); 1552 $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n"; 1553 foreach($this->settings as $setting) { 1554 $fullname = $setting->get_full_name(); 1555 if (array_key_exists($fullname, $adminroot->errors)) { 1556 $data = $adminroot->errors[$fullname]->data; 1557 } else { 1558 $data = $setting->get_setting(); 1559 if (is_null($data)) { 1560 $data = $setting->get_defaultsetting(); 1561 } 1562 } 1563 $return .= $setting->output_html($data); 1564 } 1565 $return .= '</fieldset>'; 1566 return $return; 1567 } 1568 1569 /** 1570 * Is this settigns page hidden in admin tree block? 1571 * 1572 * @return bool True if hidden 1573 */ 1574 function is_hidden() { 1575 return $this->hidden; 1576 } 1577 1578 } 1579 1580 1581 /** 1582 * Admin settings class. Only exists on setting pages. 1583 * Read & write happens at this level; no authentication. 1584 */ 1585 class admin_setting { 1586 1587 var $name; 1588 var $visiblename; 1589 var $description; 1590 var $defaultsetting; 1591 var $updatedcallback; 1592 var $plugin; // null means main config table 1593 1594 /** 1595 * Constructor 1596 * @param $name string unique ascii name 1597 * @param $visiblename string localised name 1598 * @param strin $description localised long description 1599 * @param mixed $defaultsetting string or array depending on implementation 1600 */ 1601 function admin_setting($name, $visiblename, $description, $defaultsetting) { 1602 $this->name = $name; 1603 $this->visiblename = $visiblename; 1604 $this->description = $description; 1605 $this->defaultsetting = $defaultsetting; 1606 } 1607 1608 function get_full_name() { 1609 return 's_'.$this->plugin.'_'.$this->name; 1610 } 1611 1612 function get_id() { 1613 return 'id_s_'.$this->plugin.'_'.$this->name; 1614 } 1615 1616 function config_read($name) { 1617 global $CFG; 1618 if ($this->plugin === 'backup') { 1619 require_once($CFG->dirroot.'/backup/lib.php'); 1620 $backupconfig = backup_get_config(); 1621 if (isset($backupconfig->$name)) { 1622 return $backupconfig->$name; 1623 } else { 1624 return NULL; 1625 } 1626 1627 } else if (!empty($this->plugin)) { 1628 $value = get_config($this->plugin, $name); 1629 return $value === false ? NULL : $value; 1630 1631 } else { 1632 if (isset($CFG->$name)) { 1633 return $CFG->$name; 1634 } else { 1635 return NULL; 1636 } 1637 } 1638 } 1639 1640 function config_write($name, $value) { 1641 global $CFG; 1642 if ($this->plugin === 'backup') { 1643 require_once($CFG->dirroot.'/backup/lib.php'); 1644 return (boolean)backup_set_config($name, $value); 1645 } else { 1646 return (boolean)set_config($name, $value, $this->plugin); 1647 } 1648 } 1649 1650 /** 1651 * Returns current value of this setting 1652 * @return mixed array or string depending on instance, NULL means not set yet 1653 */ 1654 function get_setting() { 1655 // has to be overridden 1656 return NULL; 1657 } 1658 1659 /** 1660 * Returns default setting if exists 1661 * @return mixed array or string depending on instance; NULL means no default, user must supply 1662 */ 1663 function get_defaultsetting() { 1664 return $this->defaultsetting; 1665 } 1666 1667 /** 1668 * Store new setting 1669 * @param mixed string or array, must not be NULL 1670 * @return '' if ok, string error message otherwise 1671 */ 1672 function write_setting($data) { 1673 // should be overridden 1674 return ''; 1675 } 1676 1677 /** 1678 * Return part of form with setting 1679 * @param mixed data array or string depending on setting 1680 * @return string 1681 */ 1682 function output_html($data, $query='') { 1683 // should be overridden 1684 return; 1685 } 1686 1687 /** 1688 * function called if setting updated - cleanup, cache reset, etc. 1689 */ 1690 function set_updatedcallback($functionname) { 1691 $this->updatedcallback = $functionname; 1692 } 1693 1694 /** 1695 * Is setting related to query text - used when searching 1696 * @param string $query 1697 * @return bool 1698 */ 1699 function is_related($query) { 1700 if (strpos(strtolower($this->name), $query) !== false) { 1701 return true; 1702 } 1703 $textlib = textlib_get_instance(); 1704 if (strpos($textlib->strtolower($this->visiblename), $query) !== false) { 1705 return true; 1706 } 1707 if (strpos($textlib->strtolower($this->description), $query) !== false) { 1708 return true; 1709 } 1710 $current = $this->get_setting(); 1711 if (!is_null($current)) { 1712 if (is_string($current)) { 1713 if (strpos($textlib->strtolower($current), $query) !== false) { 1714 return true; 1715 } 1716 } 1717 } 1718 $default = $this->get_defaultsetting(); 1719 if (!is_null($default)) { 1720 if (is_string($default)) { 1721 if (strpos($textlib->strtolower($default), $query) !== false) { 1722 return true; 1723 } 1724 } 1725 } 1726 return false; 1727 } 1728 } 1729 1730 /** 1731 * No setting - just heading and text. 1732 */ 1733 class admin_setting_heading extends admin_setting { 1734 /** 1735 * not a setting, just text 1736 * @param string $name of setting 1737 * @param string $heading heading 1738 * @param string $information text in box 1739 */ 1740 function admin_setting_heading($name, $heading, $information) { 1741 parent::admin_setting($name, $heading, $information, ''); 1742 } 1743 1744 function get_setting() { 1745 return true; 1746 } 1747 1748 function get_defaultsetting() { 1749 return true; 1750 } 1751 1752 function write_setting($data) { 1753 // do not write any setting 1754 return ''; 1755 } 1756 1757 function output_html($data, $query='') { 1758 $return = ''; 1759 if ($this->visiblename != '') { 1760 $return .= print_heading('<a name="'.$this->name.'">'.highlightfast($query, $this->visiblename).'</a>', '', 3, 'main', true); 1761 } 1762 if ($this->description != '') { 1763 $return .= print_box(highlight($query, $this->description), 'generalbox formsettingheading', '', true); 1764 } 1765 return $return; 1766 } 1767 } 1768 1769 /** 1770 * The most flexibly setting, user is typing text 1771 */ 1772 class admin_setting_configtext extends admin_setting { 1773 1774 var $paramtype; 1775 var $size; 1776 1777 /** 1778 * config text contructor 1779 * @param string $name of setting 1780 * @param string $visiblename localised 1781 * @param string $description long localised info 1782 * @param string $defaultsetting 1783 * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex 1784 * @param int $size default field size 1785 */ 1786 function admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) { 1787 $this->paramtype = $paramtype; 1788 if (!is_null($size)) { 1789 $this->size = $size; 1790 } else { 1791 $this->size = ($paramtype == PARAM_INT) ? 5 : 30; 1792 } 1793 parent::admin_setting($name, $visiblename, $description, $defaultsetting); 1794 } 1795 1796 function get_setting() { 1797 return $this->config_read($this->name); 1798 } 1799 1800 function write_setting($data) { 1801 if ($this->paramtype === PARAM_INT and $data === '') { 1802 // do not complain if '' used instead of 0 1803 $data = 0; 1804 } 1805 // $data is a string 1806 $validated = $this->validate($data); 1807 if ($validated !== true) { 1808 return $validated; 1809 } 1810 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); 1811 } 1812 1813 /** 1814 * Validate data before storage 1815 * @param string data 1816 * @return mixed true if ok string if error found 1817 */ 1818 function validate($data) { 1819 if (is_string($this->paramtype)) { 1820 if (preg_match($this->paramtype, $data)) { 1821 return true; 1822 } else { 1823 return get_string('validateerror', 'admin'); 1824 } 1825 1826 } else if ($this->paramtype === PARAM_RAW) { 1827 return true; 1828 1829 } else { 1830 $cleaned = stripslashes(clean_param(addslashes($data), $this->paramtype)); 1831 if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison 1832 return true; 1833 } else { 1834 return get_string('validateerror', 'admin'); 1835 } 1836 } 1837 } 1838 1839 function output_html($data, $query='') { 1840 $default = $this->get_defaultsetting(); 1841 1842 return format_admin_setting($this, $this->visiblename, 1843 '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>', 1844 $this->description, true, '', $default, $query); 1845 } 1846 } 1847 1848 /** 1849 * General text area without html editor. 1850 */ 1851 class admin_setting_configtextarea extends admin_setting_configtext { 1852 var $rows; 1853 var $cols; 1854 1855 function admin_setting_configtextarea($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') { 1856 $this->rows = $rows; 1857 $this->cols = $cols; 1858 parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype); 1859 } 1860 1861 function output_html($data, $query='') { 1862 $default = $this->get_defaultsetting(); 1863 1864 $defaultinfo = $default; 1865 if (!is_null($default) and $default !== '') { 1866 $defaultinfo = "\n".$default; 1867 } 1868 1869 return format_admin_setting($this, $this->visiblename, 1870 '<div class="form-textarea" ><textarea rows="'.$this->rows.'" cols="'.$this->cols.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'">'.s($data).'</textarea></div>', 1871 $this->description, true, '', $defaultinfo, $query); 1872 } 1873 } 1874 1875 /** 1876 * Password field, allows unmasking of password 1877 */ 1878 class admin_setting_configpasswordunmask extends admin_setting_configtext { 1879 /** 1880 * Constructor 1881 * @param string $name of setting 1882 * @param string $visiblename localised 1883 * @param string $description long localised info 1884 * @param string $defaultsetting default password 1885 */ 1886 function admin_setting_configpasswordunmask($name, $visiblename, $description, $defaultsetting) { 1887 parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30); 1888 } 1889 1890 function output_html($data, $query='') { 1891 $id = $this->get_id(); 1892 $unmask = get_string('unmaskpassword', 'form'); 1893 $unmaskjs = '<script type="text/javascript"> 1894 //<![CDATA[ 1895 document.write(\'<span class="unmask"><input id="'.$id.'unmask" value="1" type="checkbox" onclick="unmaskPassword(\\\''.$id.'\\\')"/><label for="'.$id.'unmask">'.addslashes_js($unmask).'<\/label><\/span>\'); 1896 document.getElementById("'.$this->get_id().'").setAttribute("autocomplete", "off"); 1897 //]]> 1898 </script>'; 1899 return format_admin_setting($this, $this->visiblename, 1900 '<div class="form-password"><input type="password" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$unmaskjs.'</div>', 1901 $this->description, true, '', NULL, $query); 1902 } 1903 } 1904 1905 /** 1906 * Path to directory 1907 */ 1908 class admin_setting_configfile extends admin_setting_configtext { 1909 /** 1910 * Constructor 1911 * @param string $name of setting 1912 * @param string $visiblename localised 1913 * @param string $description long localised info 1914 * @param string $defaultdirectory default directory location 1915 */ 1916 function admin_setting_configfile($name, $visiblename, $description, $defaultdirectory) { 1917 parent::admin_setting_configtext($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50); 1918 } 1919 1920 function output_html($data, $query='') { 1921 $default = $this->get_defaultsetting(); 1922 1923 if ($data) { 1924 if (file_exists($data)) { 1925 $executable = '<span class="pathok">✔</span>'; 1926 } else { 1927 $executable = '<span class="patherror">✘</span>'; 1928 } 1929 } else { 1930 $executable = ''; 1931 } 1932 1933 return format_admin_setting($this, $this->visiblename, 1934 '<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>', 1935 $this->description, true, '', $default, $query); 1936 } 1937 } 1938 1939 /** 1940 * Path to executable file 1941 */ 1942 class admin_setting_configexecutable extends admin_setting_configfile { 1943 1944 function output_html($data, $query='') { 1945 $default = $this->get_defaultsetting(); 1946 1947 if ($data) { 1948 if (file_exists($data) and is_executable($data)) { 1949 $executable = '<span class="pathok">✔</span>'; 1950 } else { 1951 $executable = '<span class="patherror">✘</span>'; 1952 } 1953 } else { 1954 $executable = ''; 1955 } 1956 1957 return format_admin_setting($this, $this->visiblename, 1958 '<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>', 1959 $this->description, true, '', $default, $query); 1960 } 1961 } 1962 1963 /** 1964 * Path to directory 1965 */ 1966 class admin_setting_configdirectory extends admin_setting_configfile { 1967 function output_html($data, $query='') { 1968 $default = $this->get_defaultsetting(); 1969 1970 if ($data) { 1971 if (file_exists($data) and is_dir($data)) { 1972 $executable = '<span class="pathok">✔</span>'; 1973 } else { 1974 $executable = '<span class="patherror">✘</span>'; 1975 } 1976 } else { 1977 $executable = ''; 1978 } 1979 1980 return format_admin_setting($this, $this->visiblename, 1981 '<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>', 1982 $this->description, true, '', $default, $query); 1983 } 1984 } 1985 1986 /** 1987 * Checkbox 1988 */ 1989 class admin_setting_configcheckbox extends admin_setting { 1990 var $yes; 1991 var $no; 1992 1993 /** 1994 * Constructor 1995 * @param string $name of setting 1996 * @param string $visiblename localised 1997 * @param string $description long localised info 1998 * @param string $defaultsetting 1999 * @param string $yes value used when checked 2000 * @param string $no value used when not checked 2001 */ 2002 function admin_setting_configcheckbox($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') { 2003 parent::admin_setting($name, $visiblename, $description, $defaultsetting); 2004 $this->yes = (string)$yes; 2005 $this->no = (string)$no; 2006 } 2007 2008 function get_setting() { 2009 return $this->config_read($this->name); 2010 } 2011 2012 function write_setting($data) { 2013 if ((string)$data === $this->yes) { // convert to strings before comparison 2014 $data = $this->yes; 2015 } else { 2016 $data = $this->no; 2017 } 2018 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); 2019 } 2020 2021 function output_html($data, $query='') { 2022 $default = $this->get_defaultsetting(); 2023 2024 if (!is_null($default)) { 2025 if ((string)$default === $this->yes) { 2026 $defaultinfo = get_string('checkboxyes', 'admin'); 2027 } else { 2028 $defaultinfo = get_string('checkboxno', 'admin'); 2029 } 2030 } else { 2031 $defaultinfo = NULL; 2032 } 2033 2034 if ((string)$data === $this->yes) { // convert to strings before comparison 2035 $checked = 'checked="checked"'; 2036 } else { 2037 $checked = ''; 2038 } 2039 2040 return format_admin_setting($this, $this->visiblename, 2041 '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> ' 2042 .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>', 2043 $this->description, true, '', $defaultinfo, $query); 2044 } 2045 } 2046 2047 /** 2048 * Multiple checkboxes, each represents different value, stored in csv format 2049 */ 2050 class admin_setting_configmulticheckbox extends admin_setting { 2051 var $choices; 2052 2053 /** 2054 * Constructor 2055 * @param string $name of setting 2056 * @param string $visiblename localised 2057 * @param string $description long localised info 2058 * @param array $defaultsetting array of selected 2059 * @param array $choices array of $value=>$label for each checkbox 2060 */ 2061 function admin_setting_configmulticheckbox($name, $visiblename, $description, $defaultsetting, $choices) { 2062 $this->choices = $choices; 2063 parent::admin_setting($name, $visiblename, $description, $defaultsetting); 2064 } 2065 2066 /** 2067 * This function may be used in ancestors for lazy loading of choices 2068 * @return true if loaded, false if error 2069 */ 2070 function load_choices() { 2071 /* 2072 if (is_array($this->choices)) { 2073 return true; 2074 } 2075 .... load choices here 2076 */ 2077 return true; 2078 } 2079 2080 /** 2081 * Is setting related to query text - used when searching 2082 * @param string $query 2083 * @return bool 2084 */ 2085 function is_related($query) { 2086 if (!$this->load_choices() or empty($this->choices)) { 2087 return false; 2088 } 2089 if (parent::is_related($query)) { 2090 return true; 2091 } 2092 2093 $textlib = textlib_get_instance(); 2094 foreach ($this->choices as $desc) { 2095 if (strpos($textlib->strtolower($desc), $query) !== false) { 2096 return true; 2097 } 2098 } 2099 return false; 2100 } 2101 2102 function get_setting() { 2103 $result = $this->config_read($this->name); 2104 if (is_null($result)) { 2105 return NULL; 2106 } 2107 if ($result === '') { 2108 return array(); 2109 } 2110 return explode(',', $result); 2111 } 2112 2113 function write_setting($data) { 2114 if (!is_array($data)) { 2115 return ''; // ignore it 2116 } 2117 if (!$this->load_choices() or empty($this->choices)) { 2118 return ''; 2119 } 2120 unset($data['xxxxx']); 2121 $result = array(); 2122 foreach ($data as $key => $value) { 2123 if ($value and array_key_exists($key, $this->choices)) { 2124 $result[] = $key; 2125 } 2126 } 2127 return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin'); 2128 } 2129 2130 function output_html($data, $query='') { 2131 if (!$this->load_choices() or empty($this->choices)) { 2132 return ''; 2133 } 2134 $default = $this->get_defaultsetting(); 2135 if (is_null($default)) { 2136 $default = array(); 2137 } 2138 if (is_null($data)) { 2139 foreach ($default as $key=>$value) { 2140 if ($value) { 2141 $current[] = $value; 2142 } 2143 } 2144 } 2145 2146 $options = array(); 2147 $defaults = array(); 2148 foreach($this->choices as $key=>$description) { 2149 if (in_array($key, $data)) { 2150 $checked = 'checked="checked"'; 2151 } else { 2152 $checked = ''; 2153 } 2154 if (!empty($default[$key])) { 2155 $defaults[] = $description; 2156 } 2157 2158 $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />' 2159 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>'; 2160 } 2161 2162 if (is_null(