[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * Various upgrade/install related functions and classes. 20 * 21 * @package core 22 * @subpackage upgrade 23 * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 /** UPGRADE_LOG_NORMAL = 0 */ 30 define('UPGRADE_LOG_NORMAL', 0); 31 /** UPGRADE_LOG_NOTICE = 1 */ 32 define('UPGRADE_LOG_NOTICE', 1); 33 /** UPGRADE_LOG_ERROR = 2 */ 34 define('UPGRADE_LOG_ERROR', 2); 35 36 /** 37 * Exception indicating unknown error during upgrade. 38 * 39 * @package core 40 * @subpackage upgrade 41 * @copyright 2009 Petr Skoda {@link http://skodak.org} 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 */ 44 class upgrade_exception extends moodle_exception { 45 function __construct($plugin, $version, $debuginfo=NULL) { 46 global $CFG; 47 $a = (object)array('plugin'=>$plugin, 'version'=>$version); 48 parent::__construct('upgradeerror', 'admin', "$CFG->wwwroot/$CFG->admin/index.php", $a, $debuginfo); 49 } 50 } 51 52 /** 53 * Exception indicating downgrade error during upgrade. 54 * 55 * @package core 56 * @subpackage upgrade 57 * @copyright 2009 Petr Skoda {@link http://skodak.org} 58 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 59 */ 60 class downgrade_exception extends moodle_exception { 61 function __construct($plugin, $oldversion, $newversion) { 62 global $CFG; 63 $plugin = is_null($plugin) ? 'moodle' : $plugin; 64 $a = (object)array('plugin'=>$plugin, 'oldversion'=>$oldversion, 'newversion'=>$newversion); 65 parent::__construct('cannotdowngrade', 'debug', "$CFG->wwwroot/$CFG->admin/index.php", $a); 66 } 67 } 68 69 /** 70 * @package core 71 * @subpackage upgrade 72 * @copyright 2009 Petr Skoda {@link http://skodak.org} 73 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 74 */ 75 class upgrade_requires_exception extends moodle_exception { 76 function __construct($plugin, $pluginversion, $currentmoodle, $requiremoodle) { 77 global $CFG; 78 $a = new stdClass(); 79 $a->pluginname = $plugin; 80 $a->pluginversion = $pluginversion; 81 $a->currentmoodle = $currentmoodle; 82 $a->requiremoodle = $requiremoodle; 83 parent::__construct('pluginrequirementsnotmet', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $a); 84 } 85 } 86 87 /** 88 * @package core 89 * @subpackage upgrade 90 * @copyright 2009 Petr Skoda {@link http://skodak.org} 91 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 92 */ 93 class plugin_defective_exception extends moodle_exception { 94 function __construct($plugin, $details) { 95 global $CFG; 96 parent::__construct('detectedbrokenplugin', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $plugin, $details); 97 } 98 } 99 100 /** 101 * Misplaced plugin exception. 102 * 103 * Note: this should be used only from the upgrade/admin code. 104 * 105 * @package core 106 * @subpackage upgrade 107 * @copyright 2009 Petr Skoda {@link http://skodak.org} 108 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 109 */ 110 class plugin_misplaced_exception extends moodle_exception { 111 /** 112 * Constructor. 113 * @param string $component the component from version.php 114 * @param string $expected expected directory, null means calculate 115 * @param string $current plugin directory path 116 */ 117 public function __construct($component, $expected, $current) { 118 global $CFG; 119 if (empty($expected)) { 120 list($type, $plugin) = core_component::normalize_component($component); 121 $plugintypes = core_component::get_plugin_types(); 122 if (isset($plugintypes[$type])) { 123 $expected = $plugintypes[$type] . '/' . $plugin; 124 } 125 } 126 if (strpos($expected, '$CFG->dirroot') !== 0) { 127 $expected = str_replace($CFG->dirroot, '$CFG->dirroot', $expected); 128 } 129 if (strpos($current, '$CFG->dirroot') !== 0) { 130 $current = str_replace($CFG->dirroot, '$CFG->dirroot', $current); 131 } 132 $a = new stdClass(); 133 $a->component = $component; 134 $a->expected = $expected; 135 $a->current = $current; 136 parent::__construct('detectedmisplacedplugin', 'core_plugin', "$CFG->wwwroot/$CFG->admin/index.php", $a); 137 } 138 } 139 140 /** 141 * Sets maximum expected time needed for upgrade task. 142 * Please always make sure that upgrade will not run longer! 143 * 144 * The script may be automatically aborted if upgrade times out. 145 * 146 * @category upgrade 147 * @param int $max_execution_time in seconds (can not be less than 60 s) 148 */ 149 function upgrade_set_timeout($max_execution_time=300) { 150 global $CFG; 151 152 if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) { 153 $upgraderunning = get_config(null, 'upgraderunning'); 154 } else { 155 $upgraderunning = $CFG->upgraderunning; 156 } 157 158 if (!$upgraderunning) { 159 if (CLI_SCRIPT) { 160 // never stop CLI upgrades 161 $upgraderunning = 0; 162 } else { 163 // web upgrade not running or aborted 164 print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/"); 165 } 166 } 167 168 if ($max_execution_time < 60) { 169 // protection against 0 here 170 $max_execution_time = 60; 171 } 172 173 $expected_end = time() + $max_execution_time; 174 175 if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) { 176 // no need to store new end, it is nearly the same ;-) 177 return; 178 } 179 180 if (CLI_SCRIPT) { 181 // there is no point in timing out of CLI scripts, admins can stop them if necessary 182 core_php_time_limit::raise(); 183 } else { 184 core_php_time_limit::raise($max_execution_time); 185 } 186 set_config('upgraderunning', $expected_end); // keep upgrade locked until this time 187 } 188 189 /** 190 * Upgrade savepoint, marks end of each upgrade block. 191 * It stores new main version, resets upgrade timeout 192 * and abort upgrade if user cancels page loading. 193 * 194 * Please do not make large upgrade blocks with lots of operations, 195 * for example when adding tables keep only one table operation per block. 196 * 197 * @category upgrade 198 * @param bool $result false if upgrade step failed, true if completed 199 * @param string or float $version main version 200 * @param bool $allowabort allow user to abort script execution here 201 * @return void 202 */ 203 function upgrade_main_savepoint($result, $version, $allowabort=true) { 204 global $CFG; 205 206 //sanity check to avoid confusion with upgrade_mod_savepoint usage. 207 if (!is_bool($allowabort)) { 208 $errormessage = 'Parameter type mismatch. Are you mixing up upgrade_main_savepoint() and upgrade_mod_savepoint()?'; 209 throw new coding_exception($errormessage); 210 } 211 212 if (!$result) { 213 throw new upgrade_exception(null, $version); 214 } 215 216 if ($CFG->version >= $version) { 217 // something really wrong is going on in main upgrade script 218 throw new downgrade_exception(null, $CFG->version, $version); 219 } 220 221 set_config('version', $version); 222 upgrade_log(UPGRADE_LOG_NORMAL, null, 'Upgrade savepoint reached'); 223 224 // reset upgrade timeout to default 225 upgrade_set_timeout(); 226 227 // this is a safe place to stop upgrades if user aborts page loading 228 if ($allowabort and connection_aborted()) { 229 die; 230 } 231 } 232 233 /** 234 * Module upgrade savepoint, marks end of module upgrade blocks 235 * It stores module version, resets upgrade timeout 236 * and abort upgrade if user cancels page loading. 237 * 238 * @category upgrade 239 * @param bool $result false if upgrade step failed, true if completed 240 * @param string or float $version main version 241 * @param string $modname name of module 242 * @param bool $allowabort allow user to abort script execution here 243 * @return void 244 */ 245 function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) { 246 global $DB; 247 248 $component = 'mod_'.$modname; 249 250 if (!$result) { 251 throw new upgrade_exception($component, $version); 252 } 253 254 $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version')); 255 256 if (!$module = $DB->get_record('modules', array('name'=>$modname))) { 257 print_error('modulenotexist', 'debug', '', $modname); 258 } 259 260 if ($dbversion >= $version) { 261 // something really wrong is going on in upgrade script 262 throw new downgrade_exception($component, $dbversion, $version); 263 } 264 set_config('version', $version, $component); 265 266 upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached'); 267 268 // reset upgrade timeout to default 269 upgrade_set_timeout(); 270 271 // this is a safe place to stop upgrades if user aborts page loading 272 if ($allowabort and connection_aborted()) { 273 die; 274 } 275 } 276 277 /** 278 * Blocks upgrade savepoint, marks end of blocks upgrade blocks 279 * It stores block version, resets upgrade timeout 280 * and abort upgrade if user cancels page loading. 281 * 282 * @category upgrade 283 * @param bool $result false if upgrade step failed, true if completed 284 * @param string or float $version main version 285 * @param string $blockname name of block 286 * @param bool $allowabort allow user to abort script execution here 287 * @return void 288 */ 289 function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true) { 290 global $DB; 291 292 $component = 'block_'.$blockname; 293 294 if (!$result) { 295 throw new upgrade_exception($component, $version); 296 } 297 298 $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version')); 299 300 if (!$block = $DB->get_record('block', array('name'=>$blockname))) { 301 print_error('blocknotexist', 'debug', '', $blockname); 302 } 303 304 if ($dbversion >= $version) { 305 // something really wrong is going on in upgrade script 306 throw new downgrade_exception($component, $dbversion, $version); 307 } 308 set_config('version', $version, $component); 309 310 upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached'); 311 312 // reset upgrade timeout to default 313 upgrade_set_timeout(); 314 315 // this is a safe place to stop upgrades if user aborts page loading 316 if ($allowabort and connection_aborted()) { 317 die; 318 } 319 } 320 321 /** 322 * Plugins upgrade savepoint, marks end of blocks upgrade blocks 323 * It stores plugin version, resets upgrade timeout 324 * and abort upgrade if user cancels page loading. 325 * 326 * @category upgrade 327 * @param bool $result false if upgrade step failed, true if completed 328 * @param string or float $version main version 329 * @param string $type name of plugin 330 * @param string $dir location of plugin 331 * @param bool $allowabort allow user to abort script execution here 332 * @return void 333 */ 334 function upgrade_plugin_savepoint($result, $version, $type, $plugin, $allowabort=true) { 335 global $DB; 336 337 $component = $type.'_'.$plugin; 338 339 if (!$result) { 340 throw new upgrade_exception($component, $version); 341 } 342 343 $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version')); 344 345 if ($dbversion >= $version) { 346 // Something really wrong is going on in the upgrade script 347 throw new downgrade_exception($component, $dbversion, $version); 348 } 349 set_config('version', $version, $component); 350 upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached'); 351 352 // Reset upgrade timeout to default 353 upgrade_set_timeout(); 354 355 // This is a safe place to stop upgrades if user aborts page loading 356 if ($allowabort and connection_aborted()) { 357 die; 358 } 359 } 360 361 /** 362 * Detect if there are leftovers in PHP source files. 363 * 364 * During main version upgrades administrators MUST move away 365 * old PHP source files and start from scratch (or better 366 * use git). 367 * 368 * @return bool true means borked upgrade, false means previous PHP files were properly removed 369 */ 370 function upgrade_stale_php_files_present() { 371 global $CFG; 372 373 $someexamplesofremovedfiles = array( 374 // Removed in 2.7. 375 '/admin/tool/qeupgradehelper/version.php', 376 // Removed in 2.6. 377 '/admin/block.php', 378 '/admin/oacleanup.php', 379 // Removed in 2.5. 380 '/backup/lib.php', 381 '/backup/bb/README.txt', 382 '/lib/excel/test.php', 383 // Removed in 2.4. 384 '/admin/tool/unittest/simpletestlib.php', 385 // Removed in 2.3. 386 '/lib/minify/builder/', 387 // Removed in 2.2. 388 '/lib/yui/3.4.1pr1/', 389 // Removed in 2.2. 390 '/search/cron_php5.php', 391 '/course/report/log/indexlive.php', 392 '/admin/report/backups/index.php', 393 '/admin/generator.php', 394 // Removed in 2.1. 395 '/lib/yui/2.8.0r4/', 396 // Removed in 2.0. 397 '/blocks/admin/block_admin.php', 398 '/blocks/admin_tree/block_admin_tree.php', 399 ); 400 401 foreach ($someexamplesofremovedfiles as $file) { 402 if (file_exists($CFG->dirroot.$file)) { 403 return true; 404 } 405 } 406 407 return false; 408 } 409 410 /** 411 * Upgrade plugins 412 * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype') 413 * return void 414 */ 415 function upgrade_plugins($type, $startcallback, $endcallback, $verbose) { 416 global $CFG, $DB; 417 418 /// special cases 419 if ($type === 'mod') { 420 return upgrade_plugins_modules($startcallback, $endcallback, $verbose); 421 } else if ($type === 'block') { 422 return upgrade_plugins_blocks($startcallback, $endcallback, $verbose); 423 } 424 425 $plugs = core_component::get_plugin_list($type); 426 427 foreach ($plugs as $plug=>$fullplug) { 428 // Reset time so that it works when installing a large number of plugins 429 core_php_time_limit::raise(600); 430 $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name 431 432 // check plugin dir is valid name 433 if (empty($component)) { 434 throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.'); 435 } 436 437 if (!is_readable($fullplug.'/version.php')) { 438 continue; 439 } 440 441 $plugin = new stdClass(); 442 $plugin->version = null; 443 $module = $plugin; // Prevent some notices when plugin placed in wrong directory. 444 require ($fullplug.'/version.php'); // defines $plugin with version etc 445 unset($module); 446 447 // if plugin tells us it's full name we may check the location 448 if (isset($plugin->component)) { 449 if ($plugin->component !== $component) { 450 throw new plugin_misplaced_exception($plugin->component, null, $fullplug); 451 } 452 } 453 454 if (empty($plugin->version)) { 455 throw new plugin_defective_exception($component, 'Missing version value in version.php'); 456 } 457 458 $plugin->name = $plug; 459 $plugin->fullname = $component; 460 461 if (!empty($plugin->requires)) { 462 if ($plugin->requires > $CFG->version) { 463 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires); 464 } else if ($plugin->requires < 2010000000) { 465 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.'); 466 } 467 } 468 469 // try to recover from interrupted install.php if needed 470 if (file_exists($fullplug.'/db/install.php')) { 471 if (get_config($plugin->fullname, 'installrunning')) { 472 require_once ($fullplug.'/db/install.php'); 473 $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery'; 474 if (function_exists($recover_install_function)) { 475 $startcallback($component, true, $verbose); 476 $recover_install_function(); 477 unset_config('installrunning', $plugin->fullname); 478 update_capabilities($component); 479 log_update_descriptions($component); 480 external_update_descriptions($component); 481 events_update_definition($component); 482 \core\task\manager::reset_scheduled_tasks_for_component($component); 483 message_update_providers($component); 484 \core\message\inbound\manager::update_handlers_for_component($component); 485 if ($type === 'message') { 486 message_update_processors($plug); 487 } 488 upgrade_plugin_mnet_functions($component); 489 $endcallback($component, true, $verbose); 490 } 491 } 492 } 493 494 $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching! 495 if (empty($installedversion)) { // new installation 496 $startcallback($component, true, $verbose); 497 498 /// Install tables if defined 499 if (file_exists($fullplug.'/db/install.xml')) { 500 $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml'); 501 } 502 503 /// store version 504 upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false); 505 506 /// execute post install file 507 if (file_exists($fullplug.'/db/install.php')) { 508 require_once ($fullplug.'/db/install.php'); 509 set_config('installrunning', 1, $plugin->fullname); 510 $post_install_function = 'xmldb_'.$plugin->fullname.'_install'; 511 $post_install_function(); 512 unset_config('installrunning', $plugin->fullname); 513 } 514 515 /// Install various components 516 update_capabilities($component); 517 log_update_descriptions($component); 518 external_update_descriptions($component); 519 events_update_definition($component); 520 \core\task\manager::reset_scheduled_tasks_for_component($component); 521 message_update_providers($component); 522 \core\message\inbound\manager::update_handlers_for_component($component); 523 if ($type === 'message') { 524 message_update_processors($plug); 525 } 526 upgrade_plugin_mnet_functions($component); 527 $endcallback($component, true, $verbose); 528 529 } else if ($installedversion < $plugin->version) { // upgrade 530 /// Run the upgrade function for the plugin. 531 $startcallback($component, false, $verbose); 532 533 if (is_readable($fullplug.'/db/upgrade.php')) { 534 require_once ($fullplug.'/db/upgrade.php'); // defines upgrading function 535 536 $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade'; 537 $result = $newupgrade_function($installedversion); 538 } else { 539 $result = true; 540 } 541 542 $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching! 543 if ($installedversion < $plugin->version) { 544 // store version if not already there 545 upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false); 546 } 547 548 /// Upgrade various components 549 update_capabilities($component); 550 log_update_descriptions($component); 551 external_update_descriptions($component); 552 events_update_definition($component); 553 \core\task\manager::reset_scheduled_tasks_for_component($component); 554 message_update_providers($component); 555 \core\message\inbound\manager::update_handlers_for_component($component); 556 if ($type === 'message') { 557 // Ugly hack! 558 message_update_processors($plug); 559 } 560 upgrade_plugin_mnet_functions($component); 561 $endcallback($component, false, $verbose); 562 563 } else if ($installedversion > $plugin->version) { 564 throw new downgrade_exception($component, $installedversion, $plugin->version); 565 } 566 } 567 } 568 569 /** 570 * Find and check all modules and load them up or upgrade them if necessary 571 * 572 * @global object 573 * @global object 574 */ 575 function upgrade_plugins_modules($startcallback, $endcallback, $verbose) { 576 global $CFG, $DB; 577 578 $mods = core_component::get_plugin_list('mod'); 579 580 foreach ($mods as $mod=>$fullmod) { 581 582 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it 583 continue; 584 } 585 586 $component = clean_param('mod_'.$mod, PARAM_COMPONENT); 587 588 // check module dir is valid name 589 if (empty($component)) { 590 throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.'); 591 } 592 593 if (!is_readable($fullmod.'/version.php')) { 594 throw new plugin_defective_exception($component, 'Missing version.php'); 595 } 596 597 // TODO: Support for $module will end with Moodle 2.10 by MDL-43896. Was deprecated for Moodle 2.7 by MDL-43040. 598 $plugin = new stdClass(); 599 $plugin->version = null; 600 $module = $plugin; 601 require ($fullmod .'/version.php'); // Defines $plugin with version etc. 602 $plugin = clone($module); 603 unset($module->version); 604 unset($module->component); 605 unset($module->dependencies); 606 unset($module->release); 607 608 // if plugin tells us it's full name we may check the location 609 if (isset($plugin->component)) { 610 if ($plugin->component !== $component) { 611 throw new plugin_misplaced_exception($plugin->component, null, $fullmod); 612 } 613 } 614 615 if (empty($plugin->version)) { 616 // Version must be always set now! 617 throw new plugin_defective_exception($component, 'Missing version value in version.php'); 618 } 619 620 if (!empty($plugin->requires)) { 621 if ($plugin->requires > $CFG->version) { 622 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires); 623 } else if ($plugin->requires < 2010000000) { 624 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.'); 625 } 626 } 627 628 if (empty($module->cron)) { 629 $module->cron = 0; 630 } 631 632 // all modules must have en lang pack 633 if (!is_readable("$fullmod/lang/en/$mod.php")) { 634 throw new plugin_defective_exception($component, 'Missing mandatory en language pack.'); 635 } 636 637 $module->name = $mod; // The name MUST match the directory 638 639 $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching! 640 641 if (file_exists($fullmod.'/db/install.php')) { 642 if (get_config($module->name, 'installrunning')) { 643 require_once ($fullmod.'/db/install.php'); 644 $recover_install_function = 'xmldb_'.$module->name.'_install_recovery'; 645 if (function_exists($recover_install_function)) { 646 $startcallback($component, true, $verbose); 647 $recover_install_function(); 648 unset_config('installrunning', $module->name); 649 // Install various components too 650 update_capabilities($component); 651 log_update_descriptions($component); 652 external_update_descriptions($component); 653 events_update_definition($component); 654 \core\task\manager::reset_scheduled_tasks_for_component($component); 655 message_update_providers($component); 656 \core\message\inbound\manager::update_handlers_for_component($component); 657 upgrade_plugin_mnet_functions($component); 658 $endcallback($component, true, $verbose); 659 } 660 } 661 } 662 663 if (empty($installedversion)) { 664 $startcallback($component, true, $verbose); 665 666 /// Execute install.xml (XMLDB) - must be present in all modules 667 $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml'); 668 669 /// Add record into modules table - may be needed in install.php already 670 $module->id = $DB->insert_record('modules', $module); 671 upgrade_mod_savepoint(true, $plugin->version, $module->name, false); 672 673 /// Post installation hook - optional 674 if (file_exists("$fullmod/db/install.php")) { 675 require_once("$fullmod/db/install.php"); 676 // Set installation running flag, we need to recover after exception or error 677 set_config('installrunning', 1, $module->name); 678 $post_install_function = 'xmldb_'.$module->name.'_install'; 679 $post_install_function(); 680 unset_config('installrunning', $module->name); 681 } 682 683 /// Install various components 684 update_capabilities($component); 685 log_update_descriptions($component); 686 external_update_descriptions($component); 687 events_update_definition($component); 688 \core\task\manager::reset_scheduled_tasks_for_component($component); 689 message_update_providers($component); 690 \core\message\inbound\manager::update_handlers_for_component($component); 691 upgrade_plugin_mnet_functions($component); 692 693 $endcallback($component, true, $verbose); 694 695 } else if ($installedversion < $plugin->version) { 696 /// If versions say that we need to upgrade but no upgrade files are available, notify and continue 697 $startcallback($component, false, $verbose); 698 699 if (is_readable($fullmod.'/db/upgrade.php')) { 700 require_once ($fullmod.'/db/upgrade.php'); // defines new upgrading function 701 $newupgrade_function = 'xmldb_'.$module->name.'_upgrade'; 702 $result = $newupgrade_function($installedversion, $module); 703 } else { 704 $result = true; 705 } 706 707 $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching! 708 $currmodule = $DB->get_record('modules', array('name'=>$module->name)); 709 if ($installedversion < $plugin->version) { 710 // store version if not already there 711 upgrade_mod_savepoint($result, $plugin->version, $mod, false); 712 } 713 714 // update cron flag if needed 715 if ($currmodule->cron != $module->cron) { 716 $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name)); 717 } 718 719 // Upgrade various components 720 update_capabilities($component); 721 log_update_descriptions($component); 722 external_update_descriptions($component); 723 events_update_definition($component); 724 \core\task\manager::reset_scheduled_tasks_for_component($component); 725 message_update_providers($component); 726 \core\message\inbound\manager::update_handlers_for_component($component); 727 upgrade_plugin_mnet_functions($component); 728 729 $endcallback($component, false, $verbose); 730 731 } else if ($installedversion > $plugin->version) { 732 throw new downgrade_exception($component, $installedversion, $plugin->version); 733 } 734 } 735 } 736 737 738 /** 739 * This function finds all available blocks and install them 740 * into blocks table or do all the upgrade process if newer. 741 * 742 * @global object 743 * @global object 744 */ 745 function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) { 746 global $CFG, $DB; 747 748 require_once($CFG->dirroot.'/blocks/moodleblock.class.php'); 749 750 $blocktitles = array(); // we do not want duplicate titles 751 752 //Is this a first install 753 $first_install = null; 754 755 $blocks = core_component::get_plugin_list('block'); 756 757 foreach ($blocks as $blockname=>$fullblock) { 758 759 if (is_null($first_install)) { 760 $first_install = ($DB->count_records('block_instances') == 0); 761 } 762 763 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it 764 continue; 765 } 766 767 $component = clean_param('block_'.$blockname, PARAM_COMPONENT); 768 769 // check block dir is valid name 770 if (empty($component)) { 771 throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.'); 772 } 773 774 if (!is_readable($fullblock.'/version.php')) { 775 throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.'); 776 } 777 $plugin = new stdClass(); 778 $plugin->version = null; 779 $plugin->cron = 0; 780 $module = $plugin; // Prevent some notices when module placed in wrong directory. 781 include ($fullblock.'/version.php'); 782 unset($module); 783 $block = clone($plugin); 784 unset($block->version); 785 unset($block->component); 786 unset($block->dependencies); 787 unset($block->release); 788 789 // if plugin tells us it's full name we may check the location 790 if (isset($plugin->component)) { 791 if ($plugin->component !== $component) { 792 throw new plugin_misplaced_exception($plugin->component, null, $fullblock); 793 } 794 } 795 796 if (empty($plugin->version)) { 797 throw new plugin_defective_exception($component, 'Missing block version.'); 798 } 799 800 if (!empty($plugin->requires)) { 801 if ($plugin->requires > $CFG->version) { 802 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires); 803 } else if ($plugin->requires < 2010000000) { 804 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.'); 805 } 806 } 807 808 if (!is_readable($fullblock.'/block_'.$blockname.'.php')) { 809 throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.'); 810 } 811 include_once($fullblock.'/block_'.$blockname.'.php'); 812 813 $classname = 'block_'.$blockname; 814 815 if (!class_exists($classname)) { 816 throw new plugin_defective_exception($component, 'Can not load main class.'); 817 } 818 819 $blockobj = new $classname; // This is what we'll be testing 820 $blocktitle = $blockobj->get_title(); 821 822 // OK, it's as we all hoped. For further tests, the object will do them itself. 823 if (!$blockobj->_self_test()) { 824 throw new plugin_defective_exception($component, 'Self test failed.'); 825 } 826 827 $block->name = $blockname; // The name MUST match the directory 828 829 $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching! 830 831 if (file_exists($fullblock.'/db/install.php')) { 832 if (get_config('block_'.$blockname, 'installrunning')) { 833 require_once ($fullblock.'/db/install.php'); 834 $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery'; 835 if (function_exists($recover_install_function)) { 836 $startcallback($component, true, $verbose); 837 $recover_install_function(); 838 unset_config('installrunning', 'block_'.$blockname); 839 // Install various components 840 update_capabilities($component); 841 log_update_descriptions($component); 842 external_update_descriptions($component); 843 events_update_definition($component); 844 \core\task\manager::reset_scheduled_tasks_for_component($component); 845 message_update_providers($component); 846 \core\message\inbound\manager::update_handlers_for_component($component); 847 upgrade_plugin_mnet_functions($component); 848 $endcallback($component, true, $verbose); 849 } 850 } 851 } 852 853 if (empty($installedversion)) { // block not installed yet, so install it 854 $conflictblock = array_search($blocktitle, $blocktitles); 855 if ($conflictblock !== false) { 856 // Duplicate block titles are not allowed, they confuse people 857 // AND PHP's associative arrays ;) 858 throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock))); 859 } 860 $startcallback($component, true, $verbose); 861 862 if (file_exists($fullblock.'/db/install.xml')) { 863 $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml'); 864 } 865 $block->id = $DB->insert_record('block', $block); 866 upgrade_block_savepoint(true, $plugin->version, $block->name, false); 867 868 if (file_exists($fullblock.'/db/install.php')) { 869 require_once ($fullblock.'/db/install.php'); 870 // Set installation running flag, we need to recover after exception or error 871 set_config('installrunning', 1, 'block_'.$blockname); 872 $post_install_function = 'xmldb_block_'.$blockname.'_install'; 873 $post_install_function(); 874 unset_config('installrunning', 'block_'.$blockname); 875 } 876 877 $blocktitles[$block->name] = $blocktitle; 878 879 // Install various components 880 update_capabilities($component); 881 log_update_descriptions($component); 882 external_update_descriptions($component); 883 events_update_definition($component); 884 \core\task\manager::reset_scheduled_tasks_for_component($component); 885 message_update_providers($component); 886 \core\message\inbound\manager::update_handlers_for_component($component); 887 upgrade_plugin_mnet_functions($component); 888 889 $endcallback($component, true, $verbose); 890 891 } else if ($installedversion < $plugin->version) { 892 $startcallback($component, false, $verbose); 893 894 if (is_readable($fullblock.'/db/upgrade.php')) { 895 require_once ($fullblock.'/db/upgrade.php'); // defines new upgrading function 896 $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade'; 897 $result = $newupgrade_function($installedversion, $block); 898 } else { 899 $result = true; 900 } 901 902 $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching! 903 $currblock = $DB->get_record('block', array('name'=>$block->name)); 904 if ($installedversion < $plugin->version) { 905 // store version if not already there 906 upgrade_block_savepoint($result, $plugin->version, $block->name, false); 907 } 908 909 if ($currblock->cron != $block->cron) { 910 // update cron flag if needed 911 $DB->set_field('block', 'cron', $block->cron, array('id' => $currblock->id)); 912 } 913 914 // Upgrade various components 915 update_capabilities($component); 916 log_update_descriptions($component); 917 external_update_descriptions($component); 918 events_update_definition($component); 919 \core\task\manager::reset_scheduled_tasks_for_component($component); 920 message_update_providers($component); 921 \core\message\inbound\manager::update_handlers_for_component($component); 922 upgrade_plugin_mnet_functions($component); 923 924 $endcallback($component, false, $verbose); 925 926 } else if ($installedversion > $plugin->version) { 927 throw new downgrade_exception($component, $installedversion, $plugin->version); 928 } 929 } 930 931 932 // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks 933 if ($first_install) { 934 //Iterate over each course - there should be only site course here now 935 if ($courses = $DB->get_records('course')) { 936 foreach ($courses as $course) { 937 blocks_add_default_course_blocks($course); 938 } 939 } 940 941 blocks_add_default_system_blocks(); 942 } 943 } 944 945 946 /** 947 * Log_display description function used during install and upgrade. 948 * 949 * @param string $component name of component (moodle, mod_assignment, etc.) 950 * @return void 951 */ 952 function log_update_descriptions($component) { 953 global $DB; 954 955 $defpath = core_component::get_component_directory($component).'/db/log.php'; 956 957 if (!file_exists($defpath)) { 958 $DB->delete_records('log_display', array('component'=>$component)); 959 return; 960 } 961 962 // load new info 963 $logs = array(); 964 include($defpath); 965 $newlogs = array(); 966 foreach ($logs as $log) { 967 $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name 968 } 969 unset($logs); 970 $logs = $newlogs; 971 972 $fields = array('module', 'action', 'mtable', 'field'); 973 // update all log fist 974 $dblogs = $DB->get_records('log_display', array('component'=>$component)); 975 foreach ($dblogs as $dblog) { 976 $name = $dblog->module.'-'.$dblog->action; 977 978 if (empty($logs[$name])) { 979 $DB->delete_records('log_display', array('id'=>$dblog->id)); 980 continue; 981 } 982 983 $log = $logs[$name]; 984 unset($logs[$name]); 985 986 $update = false; 987 foreach ($fields as $field) { 988 if ($dblog->$field != $log[$field]) { 989 $dblog->$field = $log[$field]; 990 $update = true; 991 } 992 } 993 if ($update) { 994 $DB->update_record('log_display', $dblog); 995 } 996 } 997 foreach ($logs as $log) { 998 $dblog = (object)$log; 999 $dblog->component = $component; 1000 $DB->insert_record('log_display', $dblog); 1001 } 1002 } 1003 1004 /** 1005 * Web service discovery function used during install and upgrade. 1006 * @param string $component name of component (moodle, mod_assignment, etc.) 1007 * @return void 1008 */ 1009 function external_update_descriptions($component) { 1010 global $DB, $CFG; 1011 1012 $defpath = core_component::get_component_directory($component).'/db/services.php'; 1013 1014 if (!file_exists($defpath)) { 1015 require_once($CFG->dirroot.'/lib/externallib.php'); 1016 external_delete_descriptions($component); 1017 return; 1018 } 1019 1020 // load new info 1021 $functions = array(); 1022 $services = array(); 1023 include($defpath); 1024 1025 // update all function fist 1026 $dbfunctions = $DB->get_records('external_functions', array('component'=>$component)); 1027 foreach ($dbfunctions as $dbfunction) { 1028 if (empty($functions[$dbfunction->name])) { 1029 $DB->delete_records('external_functions', array('id'=>$dbfunction->id)); 1030 // do not delete functions from external_services_functions, beacuse 1031 // we want to notify admins when functions used in custom services disappear 1032 1033 //TODO: this looks wrong, we have to delete it eventually (skodak) 1034 continue; 1035 } 1036 1037 $function = $functions[$dbfunction->name]; 1038 unset($functions[$dbfunction->name]); 1039 $function['classpath'] = empty($function['classpath']) ? null : $function['classpath']; 1040 1041 $update = false; 1042 if ($dbfunction->classname != $function['classname']) { 1043 $dbfunction->classname = $function['classname']; 1044 $update = true; 1045 } 1046 if ($dbfunction->methodname != $function['methodname']) { 1047 $dbfunction->methodname = $function['methodname']; 1048 $update = true; 1049 } 1050 if ($dbfunction->classpath != $function['classpath']) { 1051 $dbfunction->classpath = $function['classpath']; 1052 $update = true; 1053 } 1054 $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:''; 1055 if ($dbfunction->capabilities != $functioncapabilities) { 1056 $dbfunction->capabilities = $functioncapabilities; 1057 $update = true; 1058 } 1059 if ($update) { 1060 $DB->update_record('external_functions', $dbfunction); 1061 } 1062 } 1063 foreach ($functions as $fname => $function) { 1064 $dbfunction = new stdClass(); 1065 $dbfunction->name = $fname; 1066 $dbfunction->classname = $function['classname']; 1067 $dbfunction->methodname = $function['methodname']; 1068 $dbfunction->classpath = empty($function['classpath']) ? null : $function['classpath']; 1069 $dbfunction->component = $component; 1070 $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:''; 1071 $dbfunction->id = $DB->insert_record('external_functions', $dbfunction); 1072 } 1073 unset($functions); 1074 1075 // now deal with services 1076 $dbservices = $DB->get_records('external_services', array('component'=>$component)); 1077 foreach ($dbservices as $dbservice) { 1078 if (empty($services[$dbservice->name])) { 1079 $DB->delete_records('external_tokens', array('externalserviceid'=>$dbservice->id)); 1080 $DB->delete_records('external_services_functions', array('externalserviceid'=>$dbservice->id)); 1081 $DB->delete_records('external_services_users', array('externalserviceid'=>$dbservice->id)); 1082 $DB->delete_records('external_services', array('id'=>$dbservice->id)); 1083 continue; 1084 } 1085 $service = $services[$dbservice->name]; 1086 unset($services[$dbservice->name]); 1087 $service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled']; 1088 $service['requiredcapability'] = empty($service['requiredcapability']) ? null : $service['requiredcapability']; 1089 $service['restrictedusers'] = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers']; 1090 $service['downloadfiles'] = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles']; 1091 $service['uploadfiles'] = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles']; 1092 $service['shortname'] = !isset($service['shortname']) ? null : $service['shortname']; 1093 1094 $update = false; 1095 if ($dbservice->requiredcapability != $service['requiredcapability']) { 1096 $dbservice->requiredcapability = $service['requiredcapability']; 1097 $update = true; 1098 } 1099 if ($dbservice->restrictedusers != $service['restrictedusers']) { 1100 $dbservice->restrictedusers = $service['restrictedusers']; 1101 $update = true; 1102 } 1103 if ($dbservice->downloadfiles != $service['downloadfiles']) { 1104 $dbservice->downloadfiles = $service['downloadfiles']; 1105 $update = true; 1106 } 1107 if ($dbservice->uploadfiles != $service['uploadfiles']) { 1108 $dbservice->uploadfiles = $service['uploadfiles']; 1109 $update = true; 1110 } 1111 //if shortname is not a PARAM_ALPHANUMEXT, fail (tested here for service update and creation) 1112 if (isset($service['shortname']) and 1113 (clean_param($service['shortname'], PARAM_ALPHANUMEXT) != $service['shortname'])) { 1114 throw new moodle_exception('installserviceshortnameerror', 'webservice', '', $service['shortname']); 1115 } 1116 if ($dbservice->shortname != $service['shortname']) { 1117 //check that shortname is unique 1118 if (isset($service['shortname'])) { //we currently accepts multiple shortname == null 1119 $existingservice = $DB->get_record('external_services', 1120 array('shortname' => $service['shortname'])); 1121 if (!empty($existingservice)) { 1122 throw new moodle_exception('installexistingserviceshortnameerror', 'webservice', '', $service['shortname']); 1123 } 1124 } 1125 $dbservice->shortname = $service['shortname']; 1126 $update = true; 1127 } 1128 if ($update) { 1129 $DB->update_record('external_services', $dbservice); 1130 } 1131 1132 $functions = $DB->get_records('external_services_functions', array('externalserviceid'=>$dbservice->id)); 1133 foreach ($functions as $function) { 1134 $key = array_search($function->functionname, $service['functions']); 1135 if ($key === false) { 1136 $DB->delete_records('external_services_functions', array('id'=>$function->id)); 1137 } else { 1138 unset($service['functions'][$key]); 1139 } 1140 } 1141 foreach ($service['functions'] as $fname) { 1142 $newf = new stdClass(); 1143 $newf->externalserviceid = $dbservice->id; 1144 $newf->functionname = $fname; 1145 $DB->insert_record('external_services_functions', $newf); 1146 } 1147 unset($functions); 1148 } 1149 foreach ($services as $name => $service) { 1150 //check that shortname is unique 1151 if (isset($service['shortname'])) { //we currently accepts multiple shortname == null 1152 $existingservice = $DB->get_record('external_services', 1153 array('shortname' => $service['shortname'])); 1154 if (!empty($existingservice)) { 1155 throw new moodle_exception('installserviceshortnameerror', 'webservice'); 1156 } 1157 } 1158 1159 $dbservice = new stdClass(); 1160 $dbservice->name = $name; 1161 $dbservice->enabled = empty($service['enabled']) ? 0 : $service['enabled']; 1162 $dbservice->requiredcapability = empty($service['requiredcapability']) ? null : $service['requiredcapability']; 1163 $dbservice->restrictedusers = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers']; 1164 $dbservice->downloadfiles = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles']; 1165 $dbservice->uploadfiles = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles']; 1166 $dbservice->shortname = !isset($service['shortname']) ? null : $service['shortname']; 1167 $dbservice->component = $component; 1168 $dbservice->timecreated = time(); 1169 $dbservice->id = $DB->insert_record('external_services', $dbservice); 1170 foreach ($service['functions'] as $fname) { 1171 $newf = new stdClass(); 1172 $newf->externalserviceid = $dbservice->id; 1173 $newf->functionname = $fname; 1174 $DB->insert_record('external_services_functions', $newf); 1175 } 1176 } 1177 } 1178 1179 /** 1180 * upgrade logging functions 1181 */ 1182 function upgrade_handle_exception($ex, $plugin = null) { 1183 global $CFG; 1184 1185 // rollback everything, we need to log all upgrade problems 1186 abort_all_db_transactions(); 1187 1188 $info = get_exception_info($ex); 1189 1190 // First log upgrade error 1191 upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace); 1192 1193 // Always turn on debugging - admins need to know what is going on 1194 set_debugging(DEBUG_DEVELOPER, true); 1195 1196 default_exception_handler($ex, true, $plugin); 1197 } 1198 1199 /** 1200 * Adds log entry into upgrade_log table 1201 * 1202 * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR 1203 * @param string $plugin frankenstyle component name 1204 * @param string $info short description text of log entry 1205 * @param string $details long problem description 1206 * @param string $backtrace string used for errors only 1207 * @return void 1208 */ 1209 function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) { 1210 global $DB, $USER, $CFG; 1211 1212 if (empty($plugin)) { 1213 $plugin = 'core'; 1214 } 1215 1216 list($plugintype, $pluginname) = core_component::normalize_component($plugin); 1217 $component = is_null($pluginname) ? $plugintype : $plugintype . '_' . $pluginname; 1218 1219 $backtrace = format_backtrace($backtrace, true); 1220 1221 $currentversion = null; 1222 $targetversion = null; 1223 1224 //first try to find out current version number 1225 if ($plugintype === 'core') { 1226 //main 1227 $currentversion = $CFG->version; 1228 1229 $version = null; 1230 include("$CFG->dirroot/version.php"); 1231 $targetversion = $version; 1232 1233 } else { 1234 $pluginversion = get_config($component, 'version'); 1235 if (!empty($pluginversion)) { 1236 $currentversion = $pluginversion; 1237 } 1238 $cd = core_component::get_component_directory($component); 1239 if (file_exists("$cd/version.php")) { 1240 $plugin = new stdClass(); 1241 $plugin->version = null; 1242 $module = $plugin; 1243 include("$cd/version.php"); 1244 $targetversion = $plugin->version; 1245 } 1246 } 1247 1248 $log = new stdClass(); 1249 $log->type = $type; 1250 $log->plugin = $component; 1251 $log->version = $currentversion; 1252 $log->targetversion = $targetversion; 1253 $log->info = $info; 1254 $log->details = $details; 1255 $log->backtrace = $backtrace; 1256 $log->userid = $USER->id; 1257 $log->timemodified = time(); 1258 try { 1259 $DB->insert_record('upgrade_log', $log); 1260 } catch (Exception $ignored) { 1261 // possible during install or 2.0 upgrade 1262 } 1263 } 1264 1265 /** 1266 * Marks start of upgrade, blocks any other access to site. 1267 * The upgrade is finished at the end of script or after timeout. 1268 * 1269 * @global object 1270 * @global object 1271 * @global object 1272 */ 1273 function upgrade_started($preinstall=false) { 1274 global $CFG, $DB, $PAGE, $OUTPUT; 1275 1276 static $started = false; 1277 1278 if ($preinstall) { 1279 ignore_user_abort(true); 1280 upgrade_setup_debug(true); 1281 1282 } else if ($started) { 1283 upgrade_set_timeout(120); 1284 1285 } else { 1286 if (!CLI_SCRIPT and !$PAGE->headerprinted) { 1287 $strupgrade = get_string('upgradingversion', 'admin'); 1288 $PAGE->set_pagelayout('maintenance'); 1289 upgrade_init_javascript(); 1290 $PAGE->set_title($strupgrade.' - Moodle '.$CFG->target_release); 1291 $PAGE->set_heading($strupgrade); 1292 $PAGE->navbar->add($strupgrade); 1293 $PAGE->set_cacheable(false); 1294 echo $OUTPUT->header(); 1295 } 1296 1297 ignore_user_abort(true); 1298 core_shutdown_manager::register_function('upgrade_finished_handler'); 1299 upgrade_setup_debug(true); 1300 set_config('upgraderunning', time()+300); 1301 $started = true; 1302 } 1303 } 1304 1305 /** 1306 * Internal function - executed if upgrade interrupted. 1307 */ 1308 function upgrade_finished_handler() { 1309 upgrade_finished(); 1310 } 1311 1312 /** 1313 * Indicates upgrade is finished. 1314 * 1315 * This function may be called repeatedly. 1316 * 1317 * @global object 1318 * @global object 1319 */ 1320 function upgrade_finished($continueurl=null) { 1321 global $CFG, $DB, $OUTPUT; 1322 1323 if (!empty($CFG->upgraderunning)) { 1324 unset_config('upgraderunning'); 1325 // We have to forcefully purge the caches using the writer here. 1326 // This has to be done after we unset the config var. If someone hits the site while this is set they will 1327 // cause the config values to propogate to the caches. 1328 // Caches are purged after the last step in an upgrade but there is several code routines that exceute between 1329 // then and now that leaving a window for things to fall out of sync. 1330 cache_helper::purge_all(true); 1331 upgrade_setup_debug(false); 1332 ignore_user_abort(false); 1333 if ($continueurl) { 1334 echo $OUTPUT->continue_button($continueurl); 1335 echo $OUTPUT->footer(); 1336 die; 1337 } 1338 } 1339 } 1340 1341 /** 1342 * @global object 1343 * @global object 1344 */ 1345 function upgrade_setup_debug($starting) { 1346 global $CFG, $DB; 1347 1348 static $originaldebug = null; 1349 1350 if ($starting) { 1351 if ($originaldebug === null) { 1352 $originaldebug = $DB->get_debug(); 1353 } 1354 if (!empty($CFG->upgradeshowsql)) { 1355 $DB->set_debug(true); 1356 } 1357 } else { 1358 $DB->set_debug($originaldebug); 1359 } 1360 } 1361 1362 function print_upgrade_separator() { 1363 if (!CLI_SCRIPT) { 1364 echo '<hr />'; 1365 } 1366 } 1367 1368 /** 1369 * Default start upgrade callback 1370 * @param string $plugin 1371 * @param bool $installation true if installation, false means upgrade 1372 */ 1373 function print_upgrade_part_start($plugin, $installation, $verbose) { 1374 global $OUTPUT; 1375 if (empty($plugin) or $plugin == 'moodle') { 1376 upgrade_started($installation); // does not store upgrade running flag yet 1377 if ($verbose) { 1378 echo $OUTPUT->heading(get_string('coresystem')); 1379 } 1380 } else { 1381 upgrade_started(); 1382 if ($verbose) { 1383 echo $OUTPUT->heading($plugin); 1384 } 1385 } 1386 if ($installation) { 1387 if (empty($plugin) or $plugin == 'moodle') { 1388 // no need to log - log table not yet there ;-) 1389 } else { 1390 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin installation'); 1391 } 1392 } else { 1393 if (empty($plugin) or $plugin == 'moodle') { 1394 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting core upgrade'); 1395 } else { 1396 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin upgrade'); 1397 } 1398 } 1399 } 1400 1401 /** 1402 * Default end upgrade callback 1403 * @param string $plugin 1404 * @param bool $installation true if installation, false means upgrade 1405 */ 1406 function print_upgrade_part_end($plugin, $installation, $verbose) { 1407 global $OUTPUT; 1408 upgrade_started(); 1409 if ($installation) { 1410 if (empty($plugin) or $plugin == 'moodle') { 1411 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core installed'); 1412 } else { 1413 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin installed'); 1414 } 1415 } else { 1416 if (empty($plugin) or $plugin == 'moodle') { 1417 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core upgraded'); 1418 } else { 1419 upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin upgraded'); 1420 } 1421 } 1422 if ($verbose) { 1423 echo $OUTPUT->notification(get_string('success'), 'notifysuccess'); 1424 print_upgrade_separator(); 1425 } 1426 } 1427 1428 /** 1429 * Sets up JS code required for all upgrade scripts. 1430 * @global object 1431 */ 1432 function upgrade_init_javascript() { 1433 global $PAGE; 1434 // scroll to the end of each upgrade page so that ppl see either error or continue button, 1435 // no need to scroll continuously any more, it is enough to jump to end once the footer is printed ;-) 1436 $js = "window.scrollTo(0, 5000000);"; 1437 $PAGE->requires->js_init_code($js); 1438 } 1439 1440 /** 1441 * Try to upgrade the given language pack (or current language) 1442 * 1443 * @param string $lang the code of the language to update, defaults to the current language 1444 */ 1445 function upgrade_language_pack($lang = null) { 1446 global $CFG; 1447 1448 if (!empty($CFG->skiplangupgrade)) { 1449 return; 1450 } 1451 1452 if (!file_exists("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php")) { 1453 // weird, somebody uninstalled the import utility 1454 return; 1455 } 1456 1457 if (!$lang) { 1458 $lang = current_language(); 1459 } 1460 1461 if (!get_string_manager()->translation_exists($lang)) { 1462 return; 1463 } 1464 1465 get_string_manager()->reset_caches(); 1466 1467 if ($lang === 'en') { 1468 return; // Nothing to do 1469 } 1470 1471 upgrade_started(false); 1472 1473 require_once("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php"); 1474 tool_langimport_preupgrade_update($lang); 1475 1476 get_string_manager()->reset_caches(); 1477 1478 print_upgrade_separator(); 1479 } 1480 1481 /** 1482 * Install core moodle tables and initialize 1483 * @param float $version target version 1484 * @param bool $verbose 1485 * @return void, may throw exception 1486 */ 1487 function install_core($version, $verbose) { 1488 global $CFG, $DB; 1489 1490 // We can not call purge_all_caches() yet, make sure the temp and cache dirs exist and are empty. 1491 remove_dir($CFG->cachedir.'', true); 1492 make_cache_directory('', true); 1493 1494 remove_dir($CFG->localcachedir.'', true); 1495 make_localcache_directory('', true); 1496 1497 remove_dir($CFG->tempdir.'', true); 1498 make_temp_directory('', true); 1499 1500 remove_dir($CFG->dataroot.'/muc', true); 1501 make_writable_directory($CFG->dataroot.'/muc', true); 1502 1503 try { 1504 core_php_time_limit::raise(600); 1505 print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag 1506 1507 $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml"); 1508 upgrade_started(); // we want the flag to be stored in config table ;-) 1509 1510 // set all core default records and default settings 1511 require_once("$CFG->libdir/db/install.php"); 1512 xmldb_main_install(); // installs the capabilities too 1513 1514 // store version 1515 upgrade_main_savepoint(true, $version, false); 1516 1517 // Continue with the installation 1518 log_update_descriptions('moodle'); 1519 external_update_descriptions('moodle'); 1520 events_update_definition('moodle'); 1521 \core\task\manager::reset_scheduled_tasks_for_component('moodle'); 1522 message_update_providers('moodle'); 1523 \core\message\inbound\manager::update_handlers_for_component('moodle'); 1524 1525 // Write default settings unconditionally 1526 admin_apply_default_settings(NULL, true); 1527 1528 print_upgrade_part_end(null, true, $verbose); 1529 1530 // Purge all caches. They're disabled but this ensures that we don't have any persistent data just in case something 1531 // during installation didn't use APIs. 1532 cache_helper::purge_all(); 1533 } catch (exception $ex) { 1534 upgrade_handle_exception($ex); 1535 } 1536 } 1537 1538 /** 1539 * Upgrade moodle core 1540 * @param float $version target version 1541 * @param bool $verbose 1542 * @return void, may throw exception 1543 */ 1544 function upgrade_core($version, $verbose) { 1545 global $CFG, $SITE, $DB, $COURSE; 1546 1547 raise_memory_limit(MEMORY_EXTRA); 1548 1549 require_once($CFG->libdir.'/db/upgrade.php'); // Defines upgrades 1550 1551 try { 1552 // Reset caches before any output. 1553 cache_helper::purge_all(true); 1554 purge_all_caches(); 1555 1556 // Upgrade current language pack if we can 1557 upgrade_language_pack(); 1558 1559 print_upgrade_part_start('moodle', false, $verbose); 1560 1561 // Pre-upgrade scripts for local hack workarounds. 1562 $preupgradefile = "$CFG->dirroot/local/preupgrade.php"; 1563 if (file_exists($preupgradefile)) { 1564 core_php_time_limit::raise(); 1565 require($preupgradefile); 1566 // Reset upgrade timeout to default. 1567 upgrade_set_timeout(); 1568 } 1569 1570 $result = xmldb_main_upgrade($CFG->version); 1571 if ($version > $CFG->version) { 1572 // store version if not already there 1573 upgrade_main_savepoint($result, $version, false); 1574 } 1575 1576 // In case structure of 'course' table has been changed and we forgot to update $SITE, re-read it from db. 1577 $SITE = $DB->get_record('course', array('id' => $SITE->id)); 1578 $COURSE = clone($SITE); 1579 1580 // perform all other component upgrade routines 1581 update_capabilities('moodle'); 1582 log_update_descriptions('moodle'); 1583 external_update_descriptions('moodle'); 1584 events_update_definition('moodle'); 1585 \core\task\manager::reset_scheduled_tasks_for_component('moodle'); 1586 message_update_providers('moodle'); 1587 \core\message\inbound\manager::update_handlers_for_component('moodle'); 1588 // Update core definitions. 1589 cache_helper::update_definitions(true); 1590 1591 // Purge caches again, just to be sure we arn't holding onto old stuff now. 1592 cache_helper::purge_all(true); 1593 purge_all_caches(); 1594 1595 // Clean up contexts - more and more stuff depends on existence of paths and contexts 1596 context_helper::cleanup_instances(); 1597 context_helper::create_instances(null, false); 1598 context_helper::build_all_paths(false); 1599 $syscontext = context_system::instance(); 1600 $syscontext->mark_dirty(); 1601 1602 print_upgrade_part_end('moodle', false, $verbose); 1603 } catch (Exception $ex) { 1604 upgrade_handle_exception($ex); 1605 } 1606 } 1607 1608 /** 1609 * Upgrade/install other parts of moodle 1610 * @param bool $verbose 1611 * @return void, may throw exception 1612 */ 1613 function upgrade_noncore($verbose) { 1614 global $CFG; 1615 1616 raise_memory_limit(MEMORY_EXTRA); 1617 1618 // upgrade all plugins types 1619 try { 1620 // Reset caches before any output. 1621 cache_helper::purge_all(true); 1622 purge_all_caches(); 1623 1624 $plugintypes = core_component::get_plugin_types(); 1625 foreach ($plugintypes as $type=>$location) { 1626 upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose); 1627 } 1628 // Update cache definitions. Involves scanning each plugin for any changes. 1629 cache_helper::update_definitions(); 1630 // Mark the site as upgraded. 1631 set_config('allversionshash', core_component::get_all_versions_hash()); 1632 1633 // Purge caches again, just to be sure we arn't holding onto old stuff now. 1634 cache_helper::purge_all(true); 1635 purge_all_caches(); 1636 1637 } catch (Exception $ex) { 1638 upgrade_handle_exception($ex); 1639 } 1640 } 1641 1642 /** 1643 * Checks if the main tables have been installed yet or not. 1644 * 1645 * Note: we can not use caches here because they might be stale, 1646 * use with care! 1647 * 1648 * @return bool 1649 */ 1650 function core_tables_exist() { 1651 global $DB; 1652 1653 if (!$tables = $DB->get_tables(false) ) { // No tables yet at all. 1654 return false; 1655 1656 } else { // Check for missing main tables 1657 $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml 1658 foreach ($mtables as $mtable) { 1659 if (!in_array($mtable, $tables)) { 1660 return false; 1661 } 1662 } 1663 return true; 1664 } 1665 } 1666 1667 /** 1668 * upgrades the mnet rpc definitions for the given component. 1669 * this method doesn't return status, an exception will be thrown in the case of an error 1670 * 1671 * @param string $component the plugin to upgrade, eg auth_mnet 1672 */ 1673 function upgrade_plugin_mnet_functions($component) { 1674 global $DB, $CFG; 1675 1676 list($type, $plugin) = core_component::normalize_component($component); 1677 $path = core_component::get_plugin_directory($type, $plugin); 1678 1679 $publishes = array(); 1680 $subscribes = array(); 1681 if (file_exists($path . '/db/mnet.php')) { 1682 require_once($path . '/db/mnet.php'); // $publishes comes from this file 1683 } 1684 if (empty($publishes)) { 1685 $publishes = array(); // still need this to be able to disable stuff later 1686 } 1687 if (empty($subscribes)) { 1688 $subscribes = array(); // still need this to be able to disable stuff later 1689 } 1690 1691 static $servicecache = array(); 1692 1693 // rekey an array based on the rpc method for easy lookups later 1694 $publishmethodservices = array(); 1695 $subscribemethodservices = array(); 1696 foreach($publishes as $servicename => $service) { 1697 if (is_array($service['methods'])) { 1698 foreach($service['methods'] as $methodname) { 1699 $service['servicename'] = $servicename; 1700 $publishmethodservices[$methodname][] = $service; 1701 } 1702 } 1703 } 1704 1705 // Disable functions that don't exist (any more) in the source 1706 // Should these be deleted? What about their permissions records? 1707 foreach ($DB->get_records('mnet_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) { 1708 if (!array_key_exists($rpc->functionname, $publishmethodservices) && $rpc->enabled) { 1709 $DB->set_field('mnet_rpc', 'enabled', 0, array('id' => $rpc->id)); 1710 } else if (array_key_exists($rpc->functionname, $publishmethodservices) && !$rpc->enabled) { 1711 $DB->set_field('mnet_rpc', 'enabled', 1, array('id' => $rpc->id)); 1712 } 1713 } 1714 1715 // reflect all the services we're publishing and save them 1716 require_once($CFG->dirroot . '/lib/zend/Zend/Server/Reflection.php'); 1717 static $cachedclasses = array(); // to store reflection information in 1718 foreach ($publishes as $service => $data) { 1719 $f = $data['filename']; 1720 $c = $data['classname']; 1721 foreach ($data['methods'] as $method) { 1722 $dataobject = new stdClass(); 1723 $dataobject->plugintype = $type; 1724 $dataobject->pluginname = $plugin; 1725 $dataobject->enabled = 1; 1726 $dataobject->classname = $c; 1727 $dataobject->filename = $f; 1728 1729 if (is_string($method)) { 1730 $dataobject->functionname = $method; 1731 1732 } else if (is_array($method)) { // wants to override file or class 1733 $dataobject->functionname = $method['method']; 1734 $dataobject->classname = $method['classname']; 1735 $dataobject->filename = $method['filename']; 1736 } 1737 $dataobject->xmlrpcpath = $type.'/'.$plugin.'/'.$dataobject->filename.'/'.$method; 1738 $dataobject->static = false; 1739 1740 require_once($path . '/' . $dataobject->filename); 1741 $functionreflect = null; // slightly different ways to get this depending on whether it's a class method or a function 1742 if (!empty($dataobject->classname)) { 1743 if (!class_exists($dataobject->classname)) { 1744 throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname)); 1745 } 1746 $key = $dataobject->filename . '|' . $dataobject->classname; 1747 if (!array_key_exists($key, $cachedclasses)) { // look to see if we've already got a reflection object 1748 try { 1749 $cachedclasses[$key] = Zend_Server_Reflection::reflectClass($dataobject->classname); 1750 } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful 1751 throw new moodle_exception('installreflectionclasserror', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname, 'error' => $e->getMessage())); 1752 } 1753 } 1754 $r =& $cachedclasses[$key]; 1755 if (!$r->hasMethod($dataobject->functionname)) { 1756 throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname)); 1757 } 1758 // stupid workaround for zend not having a getMethod($name) function 1759 $ms = $r->getMethods(); 1760 foreach ($ms as $m) { 1761 if ($m->getName() == $dataobject->functionname) { 1762 $functionreflect = $m; 1763 break; 1764 } 1765 } 1766 $dataobject->static = (int)$functionreflect->isStatic(); 1767 } else { 1768 if (!function_exists($dataobject->functionname)) { 1769 throw new moodle_exception('installnosuchfunction', 'mnet', '', (object)array('method' => $dataobject->functionname, 'file' => $dataobject->filename)); 1770 } 1771 try { 1772 $functionreflect = Zend_Server_Reflection::reflectFunction($dataobject->functionname); 1773 } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful 1774 throw new moodle_exception('installreflectionfunctionerror', 'mnet', '', (object)array('method' => $dataobject->functionname, '' => $dataobject->filename, 'error' => $e->getMessage())); 1775 } 1776 } 1777 $dataobject->profile = serialize(admin_mnet_method_profile($functionreflect)); 1778 $dataobject->help = $functionreflect->getDescription(); 1779 1780 if ($record_exists = $DB->get_record('mnet_rpc', array('xmlrpcpath'=>$dataobject->xmlrpcpath))) { 1781 $dataobject->id = $record_exists->id; 1782 $dataobject->enabled = $record_exists->enabled; 1783 $DB->update_record('mnet_rpc', $dataobject); 1784 } else { 1785 $dataobject->id = $DB->insert_record('mnet_rpc', $dataobject, true); 1786 } 1787 1788 // TODO this API versioning must be reworked, here the recently processed method 1789 // sets the service API which may not be correct 1790 foreach ($publishmethodservices[$dataobject->functionname] as $service) { 1791 if ($serviceobj = $DB->get_record('mnet_service', array('name'=>$service['servicename']))) { 1792 $serviceobj->apiversion = $service['apiversion']; 1793 $DB->update_record('mnet_service', $serviceobj); 1794 } else { 1795 $serviceobj = new stdClass(); 1796 $serviceobj->name = $service['servicename']; 1797 $serviceobj->description = empty($service['description']) ? '' : $service['description']; 1798 $serviceobj->apiversion = $service['apiversion']; 1799 $serviceobj->offer = 1; 1800 $serviceobj->id = $DB->insert_record('mnet_service', $serviceobj); 1801 } 1802 $servicecache[$service['servicename']] = $serviceobj; 1803 if (!$DB->record_exists('mnet_service2rpc', array('rpcid'=>$dataobject->id, 'serviceid'=>$serviceobj->id))) { 1804 $obj = new stdClass(); 1805 $obj->rpcid = $dataobject->id; 1806 $obj->serviceid = $serviceobj->id; 1807 $DB->insert_record('mnet_service2rpc', $obj, true); 1808 } 1809 } 1810 } 1811 } 1812 // finished with methods we publish, now do subscribable methods 1813 foreach($subscribes as $service => $methods) { 1814 if (!array_key_exists($service, $servicecache)) { 1815 if (!$serviceobj = $DB->get_record('mnet_service', array('name' => $service))) { 1816 debugging("TODO: skipping unknown service $service - somebody needs to fix MDL-21993"); 1817 continue; 1818 } 1819 $servicecache[$service] = $serviceobj; 1820 } else { 1821 $serviceobj = $servicecache[$service]; 1822 } 1823 foreach ($methods as $method => $xmlrpcpath) { 1824 if (!$rpcid = $DB->get_field('mnet_remote_rpc', 'id', array('xmlrpcpath'=>$xmlrpcpath))) { 1825 $remoterpc = (object)array( 1826 'functionname' => $method, 1827 'xmlrpcpath' => $xmlrpcpath, 1828 'plugintype' => $type, 1829 'pluginname' => $plugin, 1830 'enabled' => 1, 1831 ); 1832 $rpcid = $remoterpc->id = $DB->insert_record('mnet_remote_rpc', $remoterpc, true); 1833 } 1834 if (!$DB->record_exists('mnet_remote_service2rpc', array('rpcid'=>$rpcid, 'serviceid'=>$serviceobj->id))) { 1835 $obj = new stdClass(); 1836 $obj->rpcid = $rpcid; 1837 $obj->serviceid = $serviceobj->id; 1838 $DB->insert_record('mnet_remote_service2rpc', $obj, true); 1839 } 1840 $subscribemethodservices[$method][] = $service; 1841 } 1842 } 1843 1844 foreach ($DB->get_records('mnet_remote_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) { 1845 if (!array_key_exists($rpc->functionname, $subscribemethodservices) && $rpc->enabled) { 1846 $DB->set_field('mnet_remote_rpc', 'enabled', 0, array('id' => $rpc->id)); 1847 } else if (array_key_exists($rpc->functionname, $subscribemethodservices) && !$rpc->enabled) { 1848 $DB->set_field('mnet_remote_rpc', 'enabled', 1, array('id' => $rpc->id)); 1849 } 1850 } 1851 1852 return true; 1853 } 1854 1855 /** 1856 * Given some sort of Zend Reflection function/method object, return a profile array, ready to be serialized and stored 1857 * 1858 * @param Zend_Server_Reflection_Function_Abstract $function can be any subclass of this object type 1859 * 1860 * @return array 1861 */ 1862 function admin_mnet_method_profile(Zend_Server_Reflection_Function_Abstract $function) { 1863 $protos = $function->getPrototypes(); 1864 $proto = array_pop($protos); 1865 $ret = $proto->getReturnValue(); 1866 $profile = array( 1867 'parameters' => array(), 1868 'return' => array( 1869 'type' => $ret->getType(), 1870 'description' => $ret->getDescription(), 1871 ), 1872 ); 1873 foreach ($proto->getParameters() as $p) { 1874 $profile['parameters'][] = array( 1875 'name' => $p->getName(), 1876 'type' => $p->getType(), 1877 'description' => $p->getDescription(), 1878 ); 1879 } 1880 return $profile; 1881 } 1882 1883 1884 /** 1885 * This function finds duplicate records (based on combinations of fields that should be unique) 1886 * and then progamatically generated a "most correct" version of the data, update and removing 1887 * records as appropriate 1888 * 1889 * Thanks to Dan Marsden for help 1890 * 1891 * @param string $table Table name 1892 * @param array $uniques Array of field names that should be unique 1893 * @param array $fieldstocheck Array of fields to generate "correct" data from (optional) 1894 * @return void 1895 */ 1896 function upgrade_course_completion_remove_duplicates($table, $uniques, $fieldstocheck = array()) { 1897 global $DB; 1898 1899 // Find duplicates 1900 $sql_cols = implode(', ', $uniques); 1901 1902 $sql = "SELECT {$sql_cols} FROM {{$table}} GROUP BY {$sql_cols} HAVING (count(id) > 1)"; 1903 $duplicates = $DB->get_recordset_sql($sql, array()); 1904 1905 // Loop through duplicates 1906 foreach ($duplicates as $duplicate) { 1907 $pointer = 0; 1908 1909 // Generate SQL for finding records with these duplicate uniques 1910 $sql_select = implode(' = ? AND ', $uniques).' = ?'; // builds "fieldname = ? AND fieldname = ?" 1911 $uniq_values = array(); 1912 foreach ($uniques as $u) { 1913 $uniq_values[] = $duplicate->$u; 1914 } 1915 1916 $sql_order = implode(' DESC, ', $uniques).' DESC'; // builds "fieldname DESC, fieldname DESC" 1917 1918 // Get records with these duplicate uniques 1919 $records = $DB->get_records_select( 1920 $table, 1921 $sql_select, 1922 $uniq_values, 1923 $sql_order 1924 ); 1925 1926 // Loop through and build a "correct" record, deleting the others 1927 $needsupdate = false; 1928 $origrecord = null; 1929 foreach ($records as $record) { 1930 $pointer++; 1931 if ($pointer === 1) { // keep 1st record but delete all others. 1932 $origrecord = $record; 1933 } else { 1934 // If we have fields to check, update original record 1935 if ($fieldstocheck) { 1936 // we need to keep the "oldest" of all these fields as the valid completion record. 1937 // but we want to ignore null values 1938 foreach ($fieldstocheck as $f) { 1939 if ($record->$f && (($origrecord->$f > $record->$f) || !$origrecord->$f)) { 1940 $origrecord->$f = $record->$f; 1941 $needsupdate = true; 1942 } 1943 } 1944 } 1945 $DB->delete_records($table, array('id' => $record->id)); 1946 } 1947 } 1948 if ($needsupdate || isset($origrecord->reaggregate)) { 1949 // If this table has a reaggregate field, update to force recheck on next cron run 1950 if (isset($origrecord->reaggregate)) { 1951 $origrecord->reaggregate = time(); 1952 } 1953 $DB->update_record($table, $origrecord); 1954 } 1955 } 1956 } 1957 1958 /** 1959 * Find questions missing an existing category and associate them with 1960 * a category which purpose is to gather them. 1961 * 1962 * @return void 1963 */ 1964 function upgrade_save_orphaned_questions() { 1965 global $DB; 1966 1967 // Looking for orphaned questions 1968 $orphans = $DB->record_exists_select('question', 1969 'NOT EXISTS (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)'); 1970 if (!$orphans) { 1971 return; 1972 } 1973 1974 // Generate a unique stamp for the orphaned questions category, easier to identify it later on 1975 $uniquestamp = "unknownhost+120719170400+orphan"; 1976 $systemcontext = context_system::instance(); 1977 1978 // Create the orphaned category at system level 1979 $cat = $DB->get_record('question_categories', array('stamp' => $uniquestamp, 1980 'contextid' => $systemcontext->id)); 1981 if (!$cat) { 1982 $cat = new stdClass(); 1983 $cat->parent = 0; 1984 $cat->contextid = $systemcontext->id; 1985 $cat->name = get_string('orphanedquestionscategory', 'question'); 1986 $cat->info = get_string('orphanedquestionscategoryinfo', 'question'); 1987 $cat->sortorder = 999; 1988 $cat->stamp = $uniquestamp; 1989 $cat->id = $DB->insert_record("question_categories", $cat); 1990 } 1991 1992 // Set a category to those orphans 1993 $params = array('catid' => $cat->id); 1994 $DB->execute('UPDATE {question} SET category = :catid WHERE NOT EXISTS 1995 (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)', $params); 1996 } 1997 1998 /** 1999 * Rename old backup files to current backup files. 2000 * 2001 * When added the setting 'backup_shortname' (MDL-28657) the backup file names did not contain the id of the course. 2002 * Further we fixed that behaviour by forcing the id to be always present in the file name (MDL-33812). 2003 * This function will explore the backup directory and attempt to rename the previously created files to include 2004 * the id in the name. Doing this will put them back in the process of deleting the excess backups for each course. 2005 * 2006 * This function manually recreates the file name, instead of using 2007 * {@link backup_plan_dbops::get_default_backup_filename()}, use it carefully if you're using it outside of the 2008 * usual upgrade process. 2009 * 2010 * @see backup_cron_automated_helper::remove_excess_backups() 2011 * @link http://tracker.moodle.org/browse/MDL-35116 2012 * @return void 2013 * @since Moodle 2.4 2014 */ 2015 function upgrade_rename_old_backup_files_using_shortname() { 2016 global $CFG; 2017 $dir = get_config('backup', 'backup_auto_destination'); 2018 $useshortname = get_config('backup', 'backup_shortname'); 2019 if (empty($dir) || !is_dir($dir) || !is_writable($dir)) { 2020 return; 2021 } 2022 2023 require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php'); 2024 $backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename'))); 2025 $backupword = trim(clean_filename($backupword), '_'); 2026 $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-'; 2027 $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#'; 2028 $thirtyapril = strtotime('30 April 2012 00:00'); 2029 2030 // Reading the directory. 2031 if (!$files = scandir($dir)) { 2032 return; 2033 } 2034 foreach ($files as $file) { 2035 // Skip directories and files which do not start with the common prefix. 2036 // This avoids working on files which are not related to this issue. 2037 if (!is_file($dir . '/' . $file) || !preg_match($regex, $file)) { 2038 continue; 2039 } 2040 2041 // Extract the information from the XML file. 2042 try { 2043 $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file); 2044 } catch (backup_helper_exception $e) { 2045 // Some error while retrieving the backup informations, skipping... 2046 continue; 2047 } 2048 2049 // Make sure this a course backup. 2050 if ($bcinfo->format !== backup::FORMAT_MOODLE || $bcinfo->type !== backup::TYPE_1COURSE) { 2051 continue; 2052 } 2053 2054 // Skip the backups created before the short name option was initially introduced (MDL-28657). 2055 // This was integrated on the 2nd of May 2012. Let's play safe with timezone and use the 30th of April. 2056 if ($bcinfo->backup_date < $thirtyapril) { 2057 continue; 2058 } 2059 2060 // Let's check if the file name contains the ID where it is supposed to be, if it is the case then 2061 // we will skip the file. Of course it could happen that the course ID is identical to the course short name 2062 // even though really unlikely, but then renaming this file is not necessary. If the ID is not found in the 2063 // file name then it was probably the short name which was used. 2064 $idfilename = $filename . $bcinfo->original_course_id . '-'; 2065 $idregex = '#^'.preg_quote($idfilename, '#').'.*\.mbz$#'; 2066 if (preg_match($idregex, $file)) { 2067 continue; 2068 } 2069 2070 // Generating the file name manually. We do not use backup_plan_dbops::get_default_backup_filename() because 2071 // it will query the database to get some course information, and the course could not exist any more. 2072 $newname = $filename . $bcinfo->original_course_id . '-'; 2073 if ($useshortname) { 2074 $shortname = str_replace(' ', '_', $bcinfo->original_course_shortname); 2075 $shortname = core_text::strtolower(trim(clean_filename($shortname), '_')); 2076 $newname .= $shortname . '-'; 2077 } 2078 2079 $backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig')); 2080 $date = userdate($bcinfo->backup_date, $backupdateformat, 99, false); 2081 $date = core_text::strtolower(trim(clean_filename($date), '_')); 2082 $newname .= $date; 2083 2084 if (isset($bcinfo->root_settings['users']) && !$bcinfo->root_settings['users']) { 2085 $newname .= '-nu'; 2086 } else if (isset($bcinfo->root_settings['anonymize']) && $bcinfo->root_settings['anonymize']) { 2087 $newname .= '-an'; 2088 } 2089 $newname .= '.mbz'; 2090 2091 // Final check before attempting the renaming. 2092 if ($newname == $file || file_exists($dir . '/' . $newname)) { 2093 continue; 2094 } 2095 @rename($dir . '/' . $file, $dir . '/' . $newname); 2096 } 2097 } 2098 2099 /** 2100 * Detect duplicate grade item sortorders and resort the 2101 * items to remove them. 2102 */ 2103 function upgrade_grade_item_fix_sortorder() { 2104 global $DB; 2105 2106 // The simple way to fix these sortorder duplicates would be simply to resort each 2107 // affected course. But in order to reduce the impact of this upgrade step we're trying 2108 // to do it more efficiently by doing a series of update statements rather than updating 2109 // every single grade item in affected courses. 2110 2111 $sql = "SELECT DISTINCT g1.courseid 2112 FROM {grade_items} g1 2113 JOIN {grade_items} g2 ON g1.courseid = g2.courseid 2114 WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id 2115 ORDER BY g1.courseid ASC"; 2116 foreach ($DB->get_fieldset_sql($sql) as $courseid) { 2117 $transaction = $DB->start_delegated_transaction(); 2118 $items = $DB->get_records('grade_items', array('courseid' => $courseid), '', 'id, sortorder, sortorder AS oldsort'); 2119 2120 // Get all duplicates in course order, highest sort order, and higest id first so that we can make space at the 2121 // bottom higher end of the sort orders and work down by id. 2122 $sql = "SELECT DISTINCT g1.id, g1.sortorder 2123 FROM {grade_items} g1 2124 JOIN {grade_items} g2 ON g1.courseid = g2.courseid 2125 WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id AND g1.courseid = :courseid 2126 ORDER BY g1.sortorder DESC, g1.id DESC"; 2127 2128 // This is the O(N*N) like the database version we're replacing, but at least the constants are a billion times smaller... 2129 foreach ($DB->get_records_sql($sql, array('courseid' => $courseid)) as $duplicate) { 2130 foreach ($items as $item) { 2131 if ($item->sortorder > $duplicate->sortorder || ($item->sortorder == $duplicate->sortorder && $item->id > $duplicate->id)) { 2132 $item->sortorder += 1; 2133 } 2134 } 2135 } 2136 foreach ($items as $item) { 2137 if ($item->sortorder != $item->oldsort) { 2138 $DB->update_record('grade_items', array('id' => $item->id, 'sortorder' => $item->sortorder)); 2139 } 2140 } 2141 2142 $transaction->allow_commit(); 2143 } 2144 } 2145 2146 /** 2147 * Detect file areas with missing root directory records and add them. 2148 */ 2149 function upgrade_fix_missing_root_folders() { 2150 global $DB, $USER; 2151 2152 $transaction = $DB->start_delegated_transaction(); 2153 2154 $sql = "SELECT contextid, component, filearea, itemid 2155 FROM {files} 2156 WHERE (component <> 'user' OR filearea <> 'draft') 2157 GROUP BY contextid, component, filearea, itemid 2158 HAVING MAX(CASE WHEN filename = '.' AND filepath = '/' THEN 1 ELSE 0 END) = 0"; 2159 2160 $rs = $DB->get_recordset_sql($sql); 2161 $defaults = array('filepath' => '/', 2162 'filename' => '.', 2163 'userid' => 0, // Don't rely on any particular user for these system records. 2164 'filesize' => 0, 2165 'timecreated' => time(), 2166 'timemodified' => time(), 2167 'contenthash' => sha1('')); 2168 foreach ($rs as $r) { 2169 $pathhash = sha1("/$r->contextid/$r->component/$r->filearea/$r->itemid/."); 2170 $DB->insert_record('files', (array)$r + $defaults + 2171 array('pathnamehash' => $pathhash)); 2172 } 2173 $rs->close(); 2174 $transaction->allow_commit(); 2175 } 2176 2177 /** 2178 * Detect draft file areas with missing root directory records and add them. 2179 */ 2180 function upgrade_fix_missing_root_folders_draft() { 2181 global $DB; 2182 2183 $transaction = $DB->start_delegated_transaction(); 2184 2185 $sql = "SELECT contextid, itemid, MAX(timecreated) AS timecreated, MAX(timemodified) AS timemodified 2186 FROM {files} 2187 WHERE (component = 'user' AND filearea = 'draft') 2188 GROUP BY contextid, itemid 2189 HAVING MAX(CASE WHEN filename = '.' AND filepath = '/' THEN 1 ELSE 0 END) = 0"; 2190 2191 $rs = $DB->get_recordset_sql($sql); 2192 $defaults = array('component' => 'user', 2193 'filearea' => 'draft', 2194 'filepath' => '/', 2195 'filename' => '.', 2196 'userid' => 0, // Don't rely on any particular user for these system records. 2197 'filesize' => 0, 2198 'contenthash' => sha1('')); 2199 foreach ($rs as $r) { 2200 $r->pathnamehash = sha1("/$r->contextid/user/draft/$r->itemid/."); 2201 $DB->insert_record('files', (array)$r + $defaults); 2202 } 2203 $rs->close(); 2204 $transaction->allow_commit(); 2205 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |