diff --git a/admin/cli/upgrade.php b/admin/cli/upgrade.php index cbfa2be6401..90336972e13 100644 --- a/admin/cli/upgrade.php +++ b/admin/cli/upgrade.php @@ -48,12 +48,16 @@ require_once($CFG->libdir.'/environmentlib.php'); $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang; list($options, $unrecognized) = cli_get_params( array( - 'non-interactive' => false, - 'allow-unstable' => false, - 'help' => false, - 'lang' => $lang, - 'verbose-settings' => false, - 'is-pending' => false, + 'allow-unstable' => false, + 'help' => false, + 'is-maintenance-required' => false, + 'is-pending' => false, + 'lang' => $lang, + 'maintenance' => true, + 'non-interactive' => false, + 'set-ui-upgrade-lock' => false, + 'unset-ui-upgrade-lock' => false, + 'verbose-settings' => false, ), array( 'h' => 'help' @@ -79,19 +83,35 @@ Please note you must execute this script with the same uid as apache! Site defaults may be changed via local/defaults.php. Options: ---non-interactive No interactive questions or confirmations ---allow-unstable Upgrade even if the version is not marked as stable yet, - required in non-interactive mode. ---lang=CODE Set preferred language for CLI output. Defaults to the - site language if not set. Defaults to 'en' if the lang - parameter is invalid or if the language pack is not - installed. ---verbose-settings Show new settings values. By default only the name of - new core or plugin settings are displayed. This option - outputs the new values as well as the setting name. ---is-pending If an upgrade is needed it exits with an error code of - 2 so it distinct from other types of errors. --h, --help Print out this help +--allow-unstable Upgrade even if the version is not marked as stable yet, + required in non-interactive mode. +-h, --help Print out this help. +--is-maintenance-required Returns exit code 2 if the upgrade requires maintenance mode. + Returns exit code 3 if no maintenance is required for the upgrade. +--is-pending Exit with error code 2 if an upgrade is required. +--lang=CODE Set preferred language for CLI output. Defaults to the + site language if not set. Defaults to 'en' if the lang + parameter is invalid or if the language pack is not + installed. +--maintenance Sets whether this upgrade will use maintenance mode. + If not possible, the upgrade will not happen and the script will exit. + WARNING: Caches (except theme) will be STALE and MUST be purged after upgrading. + DO NOT USE if the upgrade contains known breaking changes to the way data + and the database interact. + RECOMMENDED for lightweight deployments, to allow for a graceful purge and + rebuild of the cache. +--non-interactive No interactive questions or confirmations. +--set-ui-upgrade-lock Sets the upgrade to CLI only and unable to be triggered from the frontend. + If called with --maintenance=false, the lock WILL NOT be released when the + upgrade finishes, and MUST be manually removed. + If called with --is-maintenance-required before an upgrade, + The lock WILL be released when the upgrade finishes. +--unset-ui-upgrade-lock Removes the frontend upgrade lock, if the lock exists. + Useful when an error during the upgrade leaves the upgrade locked, + or there is need to control the time where the unlock happens. +--verbose-settings Show new settings values. By default only the name of + new core or plugin settings are displayed. This option + outputs the new values as well as the setting name. Example: \$sudo -u www-data /usr/bin/php admin/cli/upgrade.php @@ -115,12 +135,58 @@ if ($version < $CFG->version) { $oldversion = "$CFG->release ($CFG->version)"; $newversion = "$release ($version)"; -if (!moodle_needs_upgrading()) { +if ($options['unset-ui-upgrade-lock']) { + // Unconditionally unset this config if requested. + set_config('outagelessupgrade', false); + cli_writeln(get_string('cliupgradeunsetlock', 'admin')); +} + +$allhash = core_component::get_all_component_hash(); + +// Initialise allcomponent hash if not set. It will be correctly set after upgrade. +$CFG->allcomponenthash = $CFG->allcomponenthash ?? ''; +if (!$options['maintenance']) { + if ($allhash !== $CFG->allcomponenthash) { + // Throw an error here, we can't proceed, this needs to set maintenance. + cli_error(get_string('cliupgrademaintenancerequired', 'core_admin'), 2); + } + + // Set a constant to stop any upgrade var from being set during processing. + // This protects against the upgrade setting timeouts and maintenance during the upgrade. + define('CLI_UPGRADE_RUNNING', true); + + // This database control is the control to block the GUI from doing upgrade related actions. + set_config('outagelessupgrade', true); +} + +// We should ignore all upgrade locks here. +if (!moodle_needs_upgrading(false)) { cli_error(get_string('cliupgradenoneed', 'core_admin', $newversion), 0); } -if ($options['is-pending']) { - cli_error(get_string('cliupgradepending', 'core_admin'), 2); +// Handle exit based options for outputting upgrade state. +if ($options['is-pending'] || $options['is-maintenance-required']) { + // If we aren't doing a maintenance check, plain pending check. + if (!$options['is-maintenance-required']) { + cli_error(get_string('cliupgradepending', 'core_admin'), 2); + } + + // Can we do this safely with no maintenance/outage? Detect if there is a schema or other application state change. + if ($allhash !== $CFG->allcomponenthash) { + // State change here, we need to do this in maintenance. + cli_writeln(get_string('cliupgradepending', 'core_admin')); + cli_error(get_string('cliupgrademaintenancerequired', 'core_admin'), 2); + } + + // If requested, we should always set the upgrade lock here, so this cannot be run from frontend. + if ($options['set-ui-upgrade-lock']) { + set_config('outagelessupgrade', true); + cli_writeln(get_string('cliupgradesetlock', 'admin')); + } + + // We can do an upgrade without maintenance! + cli_writeln(get_string('cliupgradepending', 'core_admin')); + cli_error(get_string('cliupgrademaintenancenotrequired', 'core_admin'), 3); } // Test environment first. @@ -187,12 +253,18 @@ if ($interactive) { } if ($version > $CFG->version) { - // We purge all of MUC's caches here. - // Caches are disabled for upgrade by CACHE_DISABLE_ALL so we must set the first arg to true. - // This ensures a real config object is loaded and the stores will be purged. - // This is the only way we can purge custom caches such as memcache or APC. - // Note: all other calls to caches will still used the disabled API. - cache_helper::purge_all(true); + + // Only purge caches if this is a plain upgrade. + // In the case of a no-outage upgrade, we will gracefully roll caches after upgrade. + if ($options['maintenance']) { + // We purge all of MUC's caches here. + // Caches are disabled for upgrade by CACHE_DISABLE_ALL so we must set the first arg to true. + // This ensures a real config object is loaded and the stores will be purged. + // This is the only way we can purge custom caches such as memcache or APC. + // Note: all other calls to caches will still used the disabled API. + cache_helper::purge_all(true); + } + upgrade_core($version, true); } set_config('release', $release); @@ -230,4 +302,18 @@ foreach ($settingsoutput as $setting => $value) { upgrade_themes(); echo get_string('cliupgradefinished', 'admin', $a)."\n"; + +if (!$options['maintenance']) { + cli_writeln(get_string('cliupgradecompletenomaintenanceupgrade', 'admin')); + + // Here we check if upgrade lock has not been specifically set during this upgrade run. + // This supports wider server orchestration actions happening, which should call with no-maintenance AND set-ui-upgrade-lock, + // such as a new docker container deployment, of which the moodle upgrade is only a component. + if (!$options['set-ui-upgrade-lock']) { + // In this case we should release the lock now, as the upgrade is finished. + // We weren't told to keep the lock with set-ui-upgrade-lock, so release. + set_config('outagelessupgrade', false); + } +} + exit(0); // 0 means success diff --git a/admin/index.php b/admin/index.php index 27f474bae90..13188c385ec 100644 --- a/admin/index.php +++ b/admin/index.php @@ -302,262 +302,77 @@ if (empty($CFG->version)) { throw new \moodle_exception('missingconfigversion', 'debug'); } -// Detect config cache inconsistency, this happens when you switch branches on dev servers. -if ($CFG->version != $DB->get_field('config', 'value', array('name'=>'version'))) { - purge_all_caches(); - redirect(new moodle_url($PAGE->url), 'Config cache inconsistency detected, resetting caches...'); -} - -if (!$cache and $version > $CFG->version) { // upgrade - - $PAGE->set_url(new moodle_url($PAGE->url, array( - 'confirmupgrade' => $confirmupgrade, - 'confirmrelease' => $confirmrelease, - 'confirmplugincheck' => $confirmplugins, - ))); - - check_upgrade_key($upgradekeyhash); - - // Warning about upgrading a test site. - $testsite = false; - if (defined('BEHAT_SITE_RUNNING')) { - $testsite = 'behat'; +// If an upgrade is running, an admin page starting a frontend upgrade could corrupt the +// DB if the upgrade collided with an already running upgrade process at the wrong time. +// Pull the value direct from the DB, this needs to *always* be correct. +$outagelessupgrade = !empty($DB->get_field('config', 'value', ['name' => 'outagelessupgrade'])); +if (!$outagelessupgrade) { + // Detect config cache inconsistency, this happens when you switch branches on dev servers. + if ($CFG->version != $DB->get_field('config', 'value', array('name' => 'version'))) { + purge_all_caches(); + redirect(new moodle_url($PAGE->url), 'Config cache inconsistency detected, resetting caches...'); } - if (isset($CFG->themerev)) { - // Store the themerev to restore after purging caches. - $themerev = $CFG->themerev; - } + if (!$cache && $version > $CFG->version && !$outagelessupgrade) { // Upgrade. - // We purge all of MUC's caches here. - // Caches are disabled for upgrade by CACHE_DISABLE_ALL so we must set the first arg to true. - // This ensures a real config object is loaded and the stores will be purged. - // This is the only way we can purge custom caches such as memcache or APC. - // Note: all other calls to caches will still used the disabled API. - cache_helper::purge_all(true); - // We then purge the regular caches. - purge_all_caches(); + $PAGE->set_url(new moodle_url($PAGE->url, array( + 'confirmupgrade' => $confirmupgrade, + 'confirmrelease' => $confirmrelease, + 'confirmplugincheck' => $confirmplugins, + ))); - if (isset($themerev)) { - // Restore the themerev - set_config('themerev', $themerev); - } + check_upgrade_key($upgradekeyhash); - $output = $PAGE->get_renderer('core', 'admin'); - - if (upgrade_stale_php_files_present()) { - $PAGE->set_title($stradministration); - $PAGE->set_cacheable(false); - - echo $output->upgrade_stale_php_files_page(); - die(); - } - - if (empty($confirmupgrade)) { - $a = new stdClass(); - $a->oldversion = "$CFG->release (".sprintf('%.2f', $CFG->version).")"; - $a->newversion = "$release (".sprintf('%.2f', $version).")"; - $strdatabasechecking = get_string('databasechecking', '', $a); - - $PAGE->set_title($stradministration); - $PAGE->set_heading($strdatabasechecking); - $PAGE->set_cacheable(false); - - echo $output->upgrade_confirm_page($a->newversion, $maturity, $testsite); - die(); - - } else if (empty($confirmrelease)) { - require_once($CFG->libdir.'/environmentlib.php'); - list($envstatus, $environmentresults) = check_moodle_environment($release, ENV_SELECT_RELEASE); - $strcurrentrelease = get_string('currentrelease'); - - $PAGE->navbar->add($strcurrentrelease); - $PAGE->set_title($strcurrentrelease); - $PAGE->set_heading($strcurrentrelease); - $PAGE->set_cacheable(false); - - echo $output->upgrade_environment_page($release, $envstatus, $environmentresults); - die(); - - } else if (empty($confirmplugins)) { - $strplugincheck = get_string('plugincheck'); - - $PAGE->navbar->add($strplugincheck); - $PAGE->set_title($strplugincheck); - $PAGE->set_heading($strplugincheck); - $PAGE->set_cacheable(false); - - $pluginman = core_plugin_manager::instance(); - - // Check for available updates. - if ($fetchupdates) { - // No sesskey support guaranteed here, because sessions might not work yet. - $updateschecker = \core\update\checker::instance(); - if ($updateschecker->enabled()) { - $updateschecker->fetch(); - } - redirect($PAGE->url); + // Warning about upgrading a test site. + $testsite = false; + if (defined('BEHAT_SITE_RUNNING')) { + $testsite = 'behat'; } - // Cancel all plugin installations. - if ($abortinstallx) { - // No sesskey support guaranteed here, because sessions might not work yet. - $abortables = $pluginman->list_cancellable_installations(); - if ($abortables) { - if ($confirmabortinstall) { - foreach ($abortables as $plugin) { - $pluginman->cancel_plugin_installation($plugin->component); - } - redirect($PAGE->url); - } else { - $continue = new moodle_url($PAGE->url, array('abortinstallx' => $abortinstallx, 'confirmabortinstall' => 1)); - echo $output->upgrade_confirm_abort_install_page($abortables, $continue); - die(); - } - } - redirect($PAGE->url); + if (isset($CFG->themerev)) { + // Store the themerev to restore after purging caches. + $themerev = $CFG->themerev; } - // Cancel single plugin installation. - if ($abortinstall) { - // No sesskey support guaranteed here, because sessions might not work yet. - if ($confirmabortinstall) { - $pluginman->cancel_plugin_installation($abortinstall); - redirect($PAGE->url); - } else { - $continue = new moodle_url($PAGE->url, array('abortinstall' => $abortinstall, 'confirmabortinstall' => 1)); - $abortable = $pluginman->get_plugin_info($abortinstall); - if ($pluginman->can_cancel_plugin_installation($abortable)) { - echo $output->upgrade_confirm_abort_install_page(array($abortable), $continue); - die(); - } - redirect($PAGE->url); - } + // We purge all of MUC's caches here. + // Caches are disabled for upgrade by CACHE_DISABLE_ALL so we must set the first arg to true. + // This ensures a real config object is loaded and the stores will be purged. + // This is the only way we can purge custom caches such as memcache or APC. + // Note: all other calls to caches will still used the disabled API. + cache_helper::purge_all(true); + // We then purge the regular caches. + purge_all_caches(); + + if (isset($themerev)) { + // Restore the themerev. + set_config('themerev', $themerev); } - // Cancel all plugins upgrades (that is, restore archived versions). - if ($abortupgradex) { - // No sesskey support guaranteed here, because sessions might not work yet. - $restorable = $pluginman->list_restorable_archives(); - if ($restorable) { - upgrade_install_plugins($restorable, $confirmabortupgrade, - get_string('cancelupgradehead', 'core_plugin'), - new moodle_url($PAGE->url, array('abortupgradex' => 1, 'confirmabortupgrade' => 1)) - ); - } - redirect($PAGE->url); - } - - // Cancel single plugin upgrade (that is, install the archived version). - if ($abortupgrade) { - // No sesskey support guaranteed here, because sessions might not work yet. - $restorable = $pluginman->list_restorable_archives(); - if (isset($restorable[$abortupgrade])) { - $restorable = array($restorable[$abortupgrade]); - upgrade_install_plugins($restorable, $confirmabortupgrade, - get_string('cancelupgradehead', 'core_plugin'), - new moodle_url($PAGE->url, array('abortupgrade' => $abortupgrade, 'confirmabortupgrade' => 1)) - ); - } - redirect($PAGE->url); - } - - // Install all available missing dependencies. - if ($installdepx) { - // No sesskey support guaranteed here, because sessions might not work yet. - $installable = $pluginman->filter_installable($pluginman->missing_dependencies(true)); - upgrade_install_plugins($installable, $confirminstalldep, - get_string('dependencyinstallhead', 'core_plugin'), - new moodle_url($PAGE->url, array('installdepx' => 1, 'confirminstalldep' => 1)) - ); - } - - // Install single available missing dependency. - if ($installdep) { - // No sesskey support guaranteed here, because sessions might not work yet. - $installable = $pluginman->filter_installable($pluginman->missing_dependencies(true)); - if (!empty($installable[$installdep])) { - $installable = array($installable[$installdep]); - upgrade_install_plugins($installable, $confirminstalldep, - get_string('dependencyinstallhead', 'core_plugin'), - new moodle_url($PAGE->url, array('installdep' => $installdep, 'confirminstalldep' => 1)) - ); - } - } - - // Install all available updates. - if ($installupdatex) { - // No sesskey support guaranteed here, because sessions might not work yet. - $installable = $pluginman->filter_installable($pluginman->available_updates()); - upgrade_install_plugins($installable, $confirminstallupdate, - get_string('updateavailableinstallallhead', 'core_admin'), - new moodle_url($PAGE->url, array('installupdatex' => 1, 'confirminstallupdate' => 1)) - ); - } - - // Install single available update. - if ($installupdate and $installupdateversion) { - // No sesskey support guaranteed here, because sessions might not work yet. - if ($pluginman->is_remote_plugin_installable($installupdate, $installupdateversion)) { - $installable = array($pluginman->get_remote_plugin_info($installupdate, $installupdateversion, true)); - upgrade_install_plugins($installable, $confirminstallupdate, - get_string('updateavailableinstallallhead', 'core_admin'), - new moodle_url($PAGE->url, array('installupdate' => $installupdate, - 'installupdateversion' => $installupdateversion, 'confirminstallupdate' => 1) - ) - ); - } - } - - echo $output->upgrade_plugin_check_page(core_plugin_manager::instance(), \core\update\checker::instance(), - $version, $showallplugins, $PAGE->url, new moodle_url($PAGE->url, array('confirmplugincheck' => 1))); - die(); - - } else { - // Always verify plugin dependencies! - $failed = array(); - if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed, $CFG->branch)) { - echo $output->unsatisfied_dependencies_page($version, $failed, new moodle_url($PAGE->url, - array('confirmplugincheck' => 0))); - die(); - } - unset($failed); - - // Launch main upgrade. - upgrade_core($version, true); - } -} else if ($version < $CFG->version) { - // better stop here, we can not continue with plugin upgrades or anything else - throw new moodle_exception('downgradedcore', 'error', new moodle_url('/admin/')); -} - -// Updated human-readable release version if necessary -if (!$cache and $release <> $CFG->release) { // Update the release version - set_config('release', $release); -} - -if (!$cache and $branch <> $CFG->branch) { // Update the branch - set_config('branch', $branch); -} - -if (!$cache and moodle_needs_upgrading()) { - - $PAGE->set_url(new moodle_url($PAGE->url, array( - 'confirmrelease' => $confirmrelease, - 'confirmplugincheck' => $confirmplugins, - ))); - - check_upgrade_key($upgradekeyhash); - - if (!$PAGE->headerprinted) { - // means core upgrade or installation was not already done - - $pluginman = core_plugin_manager::instance(); $output = $PAGE->get_renderer('core', 'admin'); - if (empty($confirmrelease)) { - require_once($CFG->libdir . '/environmentlib.php'); + if (upgrade_stale_php_files_present()) { + $PAGE->set_title($stradministration); + $PAGE->set_cacheable(false); + echo $output->upgrade_stale_php_files_page(); + die(); + } + + if (empty($confirmupgrade)) { + $a = new stdClass(); + $a->oldversion = "$CFG->release (".sprintf('%.2f', $CFG->version).")"; + $a->newversion = "$release (".sprintf('%.2f', $version).")"; + $strdatabasechecking = get_string('databasechecking', '', $a); + + $PAGE->set_title($stradministration); + $PAGE->set_heading($strdatabasechecking); + $PAGE->set_cacheable(false); + + echo $output->upgrade_confirm_page($a->newversion, $maturity, $testsite); + die(); + + } else if (empty($confirmrelease)) { + require_once($CFG->libdir.'/environmentlib.php'); list($envstatus, $environmentresults) = check_moodle_environment($release, ENV_SELECT_RELEASE); $strcurrentrelease = get_string('currentrelease'); @@ -569,7 +384,7 @@ if (!$cache and moodle_needs_upgrading()) { echo $output->upgrade_environment_page($release, $envstatus, $environmentresults); die(); - } else if (!$confirmplugins) { + } else if (empty($confirmplugins)) { $strplugincheck = get_string('plugincheck'); $PAGE->navbar->add($strplugincheck); @@ -577,9 +392,11 @@ if (!$cache and moodle_needs_upgrading()) { $PAGE->set_heading($strplugincheck); $PAGE->set_cacheable(false); + $pluginman = core_plugin_manager::instance(); + // Check for available updates. if ($fetchupdates) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. $updateschecker = \core\update\checker::instance(); if ($updateschecker->enabled()) { $updateschecker->fetch(); @@ -589,7 +406,7 @@ if (!$cache and moodle_needs_upgrading()) { // Cancel all plugin installations. if ($abortinstallx) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. $abortables = $pluginman->list_cancellable_installations(); if ($abortables) { if ($confirmabortinstall) { @@ -598,8 +415,7 @@ if (!$cache and moodle_needs_upgrading()) { } redirect($PAGE->url); } else { - $continue = new moodle_url($PAGE->url, array('abortinstallx' => $abortinstallx, - 'confirmabortinstall' => 1)); + $continue = new moodle_url($PAGE->url, ['abortinstallx' => $abortinstallx, 'confirmabortinstall' => 1]); echo $output->upgrade_confirm_abort_install_page($abortables, $continue); die(); } @@ -609,7 +425,7 @@ if (!$cache and moodle_needs_upgrading()) { // Cancel single plugin installation. if ($abortinstall) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. if ($confirmabortinstall) { $pluginman->cancel_plugin_installation($abortinstall); redirect($PAGE->url); @@ -626,7 +442,7 @@ if (!$cache and moodle_needs_upgrading()) { // Cancel all plugins upgrades (that is, restore archived versions). if ($abortupgradex) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. $restorable = $pluginman->list_restorable_archives(); if ($restorable) { upgrade_install_plugins($restorable, $confirmabortupgrade, @@ -639,7 +455,7 @@ if (!$cache and moodle_needs_upgrading()) { // Cancel single plugin upgrade (that is, install the archived version). if ($abortupgrade) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. $restorable = $pluginman->list_restorable_archives(); if (isset($restorable[$abortupgrade])) { $restorable = array($restorable[$abortupgrade]); @@ -653,7 +469,7 @@ if (!$cache and moodle_needs_upgrading()) { // Install all available missing dependencies. if ($installdepx) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. $installable = $pluginman->filter_installable($pluginman->missing_dependencies(true)); upgrade_install_plugins($installable, $confirminstalldep, get_string('dependencyinstallhead', 'core_plugin'), @@ -663,7 +479,7 @@ if (!$cache and moodle_needs_upgrading()) { // Install single available missing dependency. if ($installdep) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. $installable = $pluginman->filter_installable($pluginman->missing_dependencies(true)); if (!empty($installable[$installdep])) { $installable = array($installable[$installdep]); @@ -676,7 +492,7 @@ if (!$cache and moodle_needs_upgrading()) { // Install all available updates. if ($installupdatex) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. $installable = $pluginman->filter_installable($pluginman->available_updates()); upgrade_install_plugins($installable, $confirminstallupdate, get_string('updateavailableinstallallhead', 'core_admin'), @@ -686,7 +502,7 @@ if (!$cache and moodle_needs_upgrading()) { // Install single available update. if ($installupdate and $installupdateversion) { - require_sesskey(); + // No sesskey support guaranteed here, because sessions might not work yet. if ($pluginman->is_remote_plugin_installable($installupdate, $installupdateversion)) { $installable = array($pluginman->get_remote_plugin_info($installupdate, $installupdateversion, true)); upgrade_install_plugins($installable, $confirminstallupdate, @@ -698,89 +514,280 @@ if (!$cache and moodle_needs_upgrading()) { } } - // Show plugins info. - echo $output->upgrade_plugin_check_page($pluginman, \core\update\checker::instance(), - $version, $showallplugins, - new moodle_url($PAGE->url), - new moodle_url($PAGE->url, array('confirmplugincheck' => 1, 'cache' => 0))); + echo $output->upgrade_plugin_check_page(core_plugin_manager::instance(), \core\update\checker::instance(), + $version, $showallplugins, $PAGE->url, new moodle_url($PAGE->url, array('confirmplugincheck' => 1))); die(); - } - // Make sure plugin dependencies are always checked. - $failed = array(); - if (!$pluginman->all_plugins_ok($version, $failed, $CFG->branch)) { - $output = $PAGE->get_renderer('core', 'admin'); - echo $output->unsatisfied_dependencies_page($version, $failed, new moodle_url($PAGE->url, - array('confirmplugincheck' => 0))); - die(); - } - unset($failed); - } - - // install/upgrade all plugins and other parts - upgrade_noncore(true); -} - -// If this is the first install, indicate that this site is fully configured -// except the admin password -if (during_initial_install()) { - set_config('rolesactive', 1); // after this, during_initial_install will return false. - set_config('adminsetuppending', 1); - set_config('registrationpending', 1); // Remind to register site after all other setup is finished. - - // Apply default preset, if it is defined in $CFG and has a valid value. - if (!empty($CFG->setsitepresetduringinstall)) { - \core_adminpresets\helper::change_default_preset($CFG->setsitepresetduringinstall); - } - - // we need this redirect to setup proper session - upgrade_finished("index.php?sessionstarted=1&lang=$CFG->lang"); -} - -// make sure admin user is created - this is the last step because we need -// session to be working properly in order to edit admin account - if (!empty($CFG->adminsetuppending)) { - $sessionstarted = optional_param('sessionstarted', 0, PARAM_BOOL); - if (!$sessionstarted) { - redirect("index.php?sessionstarted=1&lang=$CFG->lang"); - } else { - $sessionverify = optional_param('sessionverify', 0, PARAM_BOOL); - if (!$sessionverify) { - $SESSION->sessionverify = 1; - redirect("index.php?sessionstarted=1&sessionverify=1&lang=$CFG->lang"); } else { - if (empty($SESSION->sessionverify)) { - throw new \moodle_exception('installsessionerror', 'admin', "index.php?sessionstarted=1&lang=$CFG->lang"); + // Always verify plugin dependencies! + $failed = array(); + if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed, $CFG->branch)) { + echo $output->unsatisfied_dependencies_page($version, $failed, new moodle_url($PAGE->url, + array('confirmplugincheck' => 0))); + die(); } - unset($SESSION->sessionverify); + unset($failed); + + // Launch main upgrade. + upgrade_core($version, true); } + } else if ($version < $CFG->version) { + // Better stop here, we can not continue with plugin upgrades or anything else. + throw new moodle_exception('downgradedcore', 'error', new moodle_url('/admin/')); } - // Cleanup SESSION to make sure other code does not complain in the future. - unset($SESSION->has_timed_out); - unset($SESSION->wantsurl); + // Updated human-readable release version if necessary. + if (!$cache && $release <> $CFG->release ) { // Update the release version. + set_config('release', $release); + } - // at this stage there can be only one admin unless more were added by install - users may change username, so do not rely on that - $adminids = explode(',', $CFG->siteadmins); - $adminuser = get_complete_user_data('id', reset($adminids)); + if (!$cache && $branch <> $CFG->branch) { // Update the branch. + set_config('branch', $branch); + } - if ($adminuser->password === 'adminsetuppending') { - // prevent installation hijacking - if ($adminuser->lastip !== getremoteaddr()) { - throw new \moodle_exception('installhijacked', 'admin'); + if (!$cache && moodle_needs_upgrading()) { + + $PAGE->set_url(new moodle_url($PAGE->url, array( + 'confirmrelease' => $confirmrelease, + 'confirmplugincheck' => $confirmplugins, + ))); + + check_upgrade_key($upgradekeyhash); + + if (!$PAGE->headerprinted) { + // Means core upgrade or installation was not already done. + + $pluginman = core_plugin_manager::instance(); + $output = $PAGE->get_renderer('core', 'admin'); + + if (empty($confirmrelease)) { + require_once($CFG->libdir . '/environmentlib.php'); + + list($envstatus, $environmentresults) = check_moodle_environment($release, ENV_SELECT_RELEASE); + $strcurrentrelease = get_string('currentrelease'); + + $PAGE->navbar->add($strcurrentrelease); + $PAGE->set_title($strcurrentrelease); + $PAGE->set_heading($strcurrentrelease); + $PAGE->set_cacheable(false); + + echo $output->upgrade_environment_page($release, $envstatus, $environmentresults); + die(); + + } else if (!$confirmplugins) { + $strplugincheck = get_string('plugincheck'); + + $PAGE->navbar->add($strplugincheck); + $PAGE->set_title($strplugincheck); + $PAGE->set_heading($strplugincheck); + $PAGE->set_cacheable(false); + + // Check for available updates. + if ($fetchupdates) { + require_sesskey(); + $updateschecker = \core\update\checker::instance(); + if ($updateschecker->enabled()) { + $updateschecker->fetch(); + } + redirect($PAGE->url); + } + + // Cancel all plugin installations. + if ($abortinstallx) { + require_sesskey(); + $abortables = $pluginman->list_cancellable_installations(); + if ($abortables) { + if ($confirmabortinstall) { + foreach ($abortables as $plugin) { + $pluginman->cancel_plugin_installation($plugin->component); + } + redirect($PAGE->url); + } else { + $continue = new moodle_url($PAGE->url, array('abortinstallx' => $abortinstallx, + 'confirmabortinstall' => 1)); + echo $output->upgrade_confirm_abort_install_page($abortables, $continue); + die(); + } + } + redirect($PAGE->url); + } + + // Cancel single plugin installation. + if ($abortinstall) { + require_sesskey(); + if ($confirmabortinstall) { + $pluginman->cancel_plugin_installation($abortinstall); + redirect($PAGE->url); + } else { + $continue = new moodle_url($PAGE->url, array('abortinstall' => $abortinstall, 'confirmabortinstall' => 1)); + $abortable = $pluginman->get_plugin_info($abortinstall); + if ($pluginman->can_cancel_plugin_installation($abortable)) { + echo $output->upgrade_confirm_abort_install_page(array($abortable), $continue); + die(); + } + redirect($PAGE->url); + } + } + + // Cancel all plugins upgrades (that is, restore archived versions). + if ($abortupgradex) { + require_sesskey(); + $restorable = $pluginman->list_restorable_archives(); + if ($restorable) { + upgrade_install_plugins($restorable, $confirmabortupgrade, + get_string('cancelupgradehead', 'core_plugin'), + new moodle_url($PAGE->url, array('abortupgradex' => 1, 'confirmabortupgrade' => 1)) + ); + } + redirect($PAGE->url); + } + + // Cancel single plugin upgrade (that is, install the archived version). + if ($abortupgrade) { + require_sesskey(); + $restorable = $pluginman->list_restorable_archives(); + if (isset($restorable[$abortupgrade])) { + $restorable = array($restorable[$abortupgrade]); + upgrade_install_plugins($restorable, $confirmabortupgrade, + get_string('cancelupgradehead', 'core_plugin'), + new moodle_url($PAGE->url, array('abortupgrade' => $abortupgrade, 'confirmabortupgrade' => 1)) + ); + } + redirect($PAGE->url); + } + + // Install all available missing dependencies. + if ($installdepx) { + require_sesskey(); + $installable = $pluginman->filter_installable($pluginman->missing_dependencies(true)); + upgrade_install_plugins($installable, $confirminstalldep, + get_string('dependencyinstallhead', 'core_plugin'), + new moodle_url($PAGE->url, array('installdepx' => 1, 'confirminstalldep' => 1)) + ); + } + + // Install single available missing dependency. + if ($installdep) { + require_sesskey(); + $installable = $pluginman->filter_installable($pluginman->missing_dependencies(true)); + if (!empty($installable[$installdep])) { + $installable = array($installable[$installdep]); + upgrade_install_plugins($installable, $confirminstalldep, + get_string('dependencyinstallhead', 'core_plugin'), + new moodle_url($PAGE->url, array('installdep' => $installdep, 'confirminstalldep' => 1)) + ); + } + } + + // Install all available updates. + if ($installupdatex) { + require_sesskey(); + $installable = $pluginman->filter_installable($pluginman->available_updates()); + upgrade_install_plugins($installable, $confirminstallupdate, + get_string('updateavailableinstallallhead', 'core_admin'), + new moodle_url($PAGE->url, array('installupdatex' => 1, 'confirminstallupdate' => 1)) + ); + } + + // Install single available update. + if ($installupdate && $installupdateversion) { + require_sesskey(); + if ($pluginman->is_remote_plugin_installable($installupdate, $installupdateversion)) { + $installable = array($pluginman->get_remote_plugin_info($installupdate, $installupdateversion, true)); + upgrade_install_plugins($installable, $confirminstallupdate, + get_string('updateavailableinstallallhead', 'core_admin'), + new moodle_url($PAGE->url, array('installupdate' => $installupdate, + 'installupdateversion' => $installupdateversion, 'confirminstallupdate' => 1) + ) + ); + } + } + + // Show plugins info. + echo $output->upgrade_plugin_check_page($pluginman, \core\update\checker::instance(), + $version, $showallplugins, + new moodle_url($PAGE->url), + new moodle_url($PAGE->url, array('confirmplugincheck' => 1, 'cache' => 0))); + die(); + } + + // Make sure plugin dependencies are always checked. + $failed = array(); + if (!$pluginman->all_plugins_ok($version, $failed, $CFG->branch)) { + $output = $PAGE->get_renderer('core', 'admin'); + echo $output->unsatisfied_dependencies_page($version, $failed, new moodle_url($PAGE->url, + array('confirmplugincheck' => 0))); + die(); + } + unset($failed); + } + + // Install/upgrade all plugins and other parts. + upgrade_noncore(true); + } + + // If this is the first install, indicate that this site is fully configured, + // Except the admin password. + if (during_initial_install()) { + set_config('rolesactive', 1); // After this, during_initial_install will return false. + set_config('adminsetuppending', 1); + set_config('registrationpending', 1); // Remind to register site after all other setup is finished. + + // Apply default preset, if it is defined in $CFG and has a valid value. + if (!empty($CFG->setsitepresetduringinstall)) { + \core_adminpresets\helper::change_default_preset($CFG->setsitepresetduringinstall); + } + + // We need this redirect to setup proper session. + upgrade_finished("index.php?sessionstarted=1&lang=$CFG->lang"); + } + + // Make sure admin user is created - this is the last step, + // We need session to be working properly in order to edit admin account. + if (!empty($CFG->adminsetuppending)) { + $sessionstarted = optional_param('sessionstarted', 0, PARAM_BOOL); + if (!$sessionstarted) { + redirect("index.php?sessionstarted=1&lang=$CFG->lang"); + } else { + $sessionverify = optional_param('sessionverify', 0, PARAM_BOOL); + if (!$sessionverify) { + $SESSION->sessionverify = 1; + redirect("index.php?sessionstarted=1&sessionverify=1&lang=$CFG->lang"); + } else { + if (empty($SESSION->sessionverify)) { + throw new \moodle_exception('installsessionerror', 'admin', "index.php?sessionstarted=1&lang=$CFG->lang"); + } + unset($SESSION->sessionverify); + } + } + + // Cleanup SESSION to make sure other code does not complain in the future. + unset($SESSION->has_timed_out); + unset($SESSION->wantsurl); + + // At this stage there can be only one admin unless more were added by install, + // Users may change username, so do not rely on that. + $adminids = explode(',', $CFG->siteadmins); + $adminuser = get_complete_user_data('id', reset($adminids)); + + if ($adminuser->password === 'adminsetuppending') { + // Prevent installation hijacking. + if ($adminuser->lastip !== getremoteaddr()) { + throw new \moodle_exception('installhijacked', 'admin'); + } + // Login user and let him set password and admin details. + $adminuser->newadminuser = 1; + complete_user_login($adminuser); + redirect("$CFG->wwwroot/user/editadvanced.php?id=$adminuser->id"); // Edit thyself. + + } else { + unset_config('adminsetuppending'); } - // login user and let him set password and admin details - $adminuser->newadminuser = 1; - complete_user_login($adminuser); - redirect("$CFG->wwwroot/user/editadvanced.php?id=$adminuser->id"); // Edit thyself } else { - unset_config('adminsetuppending'); + // Just make sure upgrade logging is properly terminated. + upgrade_finished('upgradesettings.php'); } - -} else { - // just make sure upgrade logging is properly terminated - upgrade_finished('upgradesettings.php'); } if (has_capability('moodle/site:config', context_system::instance())) { diff --git a/lang/en/admin.php b/lang/en/admin.php index 98bd0bd8857..4bbd3e6f49b 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -138,12 +138,24 @@ $string['clitypevaluedefault'] = 'type value, press Enter to use default value ( $string['cliunknowoption'] = 'Unrecognised options: {$a} Please use --help option.'; +$string['cliupgradecompletenomaintenanceupgrade'] = 'To purge remaining caches after user traffic cutover to new code: + + php admin/cli/purge_caches.php --muc + php admin/cli/purge_caches.php --js + php admin/cli/purge_caches.php --filter + php admin/cli/purge_caches.php --other + +It is recommended to perform these purges in isolation, with a gap between commands, to reduce load spikes on the web server.'; $string['cliupgradedefault'] = 'New setting: {$a}'; $string['cliupgradedefaultheading'] = 'Setting new default values'; $string['cliupgradedefaultverbose'] = 'New setting: {$a->name}, Default value: {$a->defaultsetting}'; $string['cliupgradefinished'] = 'Command line upgrade from {$a->oldversion} to {$a->newversion} completed successfully.'; +$string['cliupgrademaintenancenotrequired'] = 'This upgrade WILL NOT result in maintenance mode for users.'; +$string['cliupgrademaintenancerequired'] = 'This upgrade WILL result in maintenance mode for users.'; $string['cliupgradenoneed'] = 'No upgrade needed for the installed version {$a}. Thanks for coming anyway!'; $string['cliupgradepending'] = 'An upgrade is pending'; +$string['cliupgradesetlock'] = 'Upgrade has been locked to CLI execution only.'; +$string['cliupgradeunsetlock'] = 'Existing CLI execution upgrade lock has been removed.'; $string['cliyesnoprompt'] = 'type y (means yes) or n (means no)'; $string['close'] = 'Close'; $string['commentsperpage'] = 'Comments displayed per page'; diff --git a/lib/classes/component.php b/lib/classes/component.php index eb535292c5a..047a4fb4797 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -1195,6 +1195,80 @@ $cache = '.var_export($cache, true).'; return $versions; } + /** + * Returns hash of all core + plugin /db/ directories. + * + * This is relatively slow and not fully cached, use with care! + * + * @param array|null $components optional component directory => hash array to use. Only used in PHPUnit. + * @return string sha1 hash. + */ + public static function get_all_component_hash(?array $components = null) : string { + $tohash = $components ?? self::get_all_directory_hashes(); + return sha1(serialize($tohash)); + } + + /** + * Get the hashes of all core + plugin /db/ directories. + * + * @param array|null $directories optional component directory array to hash. Only used in PHPUnit. + * @return array of directory => hash. + */ + public static function get_all_directory_hashes(?array $directories = null) : array { + global $CFG; + + self::init(); + + // The problem here is that the component cache might be stale, + // we want this to work also on frontpage without resetting the component cache. + $usecache = false; + if (CACHE_DISABLE_ALL || (defined('IGNORE_COMPONENT_CACHE') && IGNORE_COMPONENT_CACHE)) { + $usecache = true; + } + + if (empty($directories)) { + $directories = [ + $CFG->libdir . '/db' + ]; + // For all components, get the directory of the /db directory. + $plugintypes = self::get_plugin_types(); + foreach ($plugintypes as $type => $typedir) { + if ($usecache) { + $plugs = self::get_plugin_list($type); + } else { + $plugs = self::fetch_plugins($type, $typedir); + } + foreach ($plugs as $plug) { + $directories[] = $plug . '/db'; + } + } + } + + // Create a mapping of directories to their hash. + $hashes = []; + foreach ($directories as $directory) { + if (!is_dir($directory)) { + // Just hash an empty string as the non-existing representation. + $hashes[$directory] = sha1(''); + continue; + } + + $scan = scandir($directory); + $scanhashes = []; + foreach ($scan as $file) { + $file = $directory . '/' . $file; + // Moodle ignores directories. + if (!is_dir($file)) { + $scanhashes[] = hash_file('sha1', $file); + } + } + // Finally we can serialize and hash the whole dir. + $hashes[$directory] = sha1(serialize($scanhashes)); + } + + return $hashes; + } + /** * Invalidate opcode cache for given file, this is intended for * php files that are stored in dataroot. diff --git a/lib/moodlelib.php b/lib/moodlelib.php index cb23fab7eef..3db5c2bb124 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -8231,10 +8231,23 @@ function check_php_version($version='5.2.4') { * Checks version numbers of main code and all plugins to see * if there are any mismatches. * + * @param bool $checkupgradeflag check the outagelessupgrade flag to see if an upgrade is running. * @return bool */ -function moodle_needs_upgrading() { - global $CFG; +function moodle_needs_upgrading($checkupgradeflag = true) { + global $CFG, $DB; + + // Say no if there is already an upgrade running. + if ($checkupgradeflag) { + $lock = $DB->get_field('config', 'value', ['name' => 'outagelessupgrade']); + $currentprocessrunningupgrade = (defined('CLI_UPGRADE_RUNNING') && CLI_UPGRADE_RUNNING); + // If we ARE locked, but this PHP process is NOT the process running the upgrade, + // We should always return false. + // This means the upgrade is running from CLI somewhere, or about to. + if (!empty($lock) && !$currentprocessrunningupgrade) { + return false; + } + } if (empty($CFG->version)) { return true; diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index fd58efcee91..5ae4d2b51ce 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -946,4 +946,35 @@ class component_test extends advanced_testcase { // The function will return false for a non-existent component. $this->assertFalse(core_component::has_monologo_icon('randomcomponent', 'h5p')); } + + /* + * Tests the getter for the db directory summary hash. + * + * @covers \core_component::get_all_directory_hashes + */ + public function test_get_db_directories_hash() { + $initial = \core_component::get_all_component_hash(); + + $dir = make_request_directory(); + $hashes = \core_component::get_all_directory_hashes([$dir]); + $emptydirhash = \core_component::get_all_component_hash([$hashes]); + + // Confirm that a single empty directory is a different hash to the core hash. + $this->assertNotEquals($initial, $emptydirhash); + + // Now lets add something to the dir, and check the hash is different. + $file = fopen($dir . '/test.php', 'w'); + fwrite($file, 'sometestdata'); + fclose($file); + + $hashes = \core_component::get_all_directory_hashes([$dir]); + $onefiledirhash = \core_component::get_all_component_hash([$hashes]); + $this->assertNotEquals($emptydirhash, $onefiledirhash); + + // Now add a subdirectory inside the request dir. This should not affect the hash. + mkdir($dir . '/subdir'); + $hashes = \core_component::get_all_directory_hashes([$dir]); + $finalhash = \core_component::get_all_component_hash([$hashes]); + $this->assertEquals($onefiledirhash, $finalhash); + } } diff --git a/lib/tests/upgradelib_test.php b/lib/tests/upgradelib_test.php index 4595477ab43..f3dd010a1e9 100644 --- a/lib/tests/upgradelib_test.php +++ b/lib/tests/upgradelib_test.php @@ -1504,4 +1504,108 @@ calendar,core_calendar|/calendar/view.php?view=month', $this->assertTrue($updatedold->timecreated >= $origtime); $this->assertTrue($updatedold->timemodified >= $origtime); } + + /** + * Test the upgrade status check alongside the outageless flags. + * + * @covers ::moodle_needs_upgrading + */ + public function test_moodle_upgrade_check_outageless() { + global $CFG; + $this->resetAfterTest(); + // Get a baseline. + $this->assertFalse(moodle_needs_upgrading()); + + // First lets check a plain upgrade ready. + $CFG->version = ''; + $this->assertTrue(moodle_needs_upgrading()); + + // Now set the locking config and confirm we shouldn't upgrade. + set_config('outagelessupgrade', true); + $this->assertFalse(moodle_needs_upgrading()); + + // Test the ignorelock flag is functioning. + $this->assertTrue(moodle_needs_upgrading(false)); + } + + /** + * Test the upgrade status check alongside the outageless flags. + * + * @covers ::upgrade_started + */ + public function test_moodle_start_upgrade_outageless() { + global $CFG; + $this->resetAfterTest(); + $this->assertObjectNotHasAttribute('upgraderunning', $CFG); + + // Confirm that starting normally sets the upgraderunning flag. + upgrade_started(); + $upgrade = get_config('core', 'upgraderunning'); + $this->assertTrue($upgrade > (time() - 5)); + + // Confirm that the config flag doesnt affect the internal upgrade processes. + unset($CFG->upgraderunning); + set_config('upgraderunning', null); + set_config('outagelessupgrade', true); + upgrade_started(); + $upgrade = get_config('core', 'upgraderunning'); + $this->assertTrue($upgrade > (time() - 5)); + } + + /** + * Test the upgrade timeout setter alongside the outageless flags. + * + * @covers ::upgrade_set_timeout + */ + public function test_moodle_set_upgrade_timeout_outageless() { + global $CFG; + $this->resetAfterTest(); + $this->assertObjectNotHasAttribute('upgraderunning', $CFG); + + // Confirm running normally sets the timeout. + upgrade_set_timeout(120); + $upgrade = get_config('core', 'upgraderunning'); + $this->assertTrue($upgrade > (time() - 5)); + + // Confirm that the config flag doesnt affect the internal upgrade processes. + unset($CFG->upgraderunning); + set_config('upgraderunning', null); + set_config('outagelessupgrade', true); + upgrade_set_timeout(120); + $upgrade = get_config('core', 'upgraderunning'); + $this->assertTrue($upgrade > (time() - 5)); + } + + /** + * Test the components of the upgrade process being run outageless. + * + * @covers ::moodle_needs_upgrading + * @covers ::upgrade_started + * @covers ::upgrade_set_timeout + */ + public function test_upgrade_components_with_outageless() { + global $CFG; + $this->resetAfterTest(); + + // We can now define the outageless constant for use in upgrade, and test the effects. + define('CLI_UPGRADE_RUNNING', true); + + // First test the upgrade check. Even when locked via config this should return true. + // This can happen when attempting to fix a broken upgrade, so needs to work. + set_config('outagelessupgrade', true); + $CFG->version = ''; + $this->assertTrue(moodle_needs_upgrading()); + + // Now confirm that starting upgrade with the constant will not set the upgraderunning flag. + set_config('upgraderunning', null); + upgrade_started(); + $upgrade = get_config('core', 'upgraderunning'); + $this->assertFalse($upgrade); + + // The same for timeouts, it should not be set if the constant is set. + set_config('upgraderunning', null); + upgrade_set_timeout(120); + $upgrade = get_config('core', 'upgraderunning'); + $this->assertFalse($upgrade); + } } diff --git a/lib/upgradelib.php b/lib/upgradelib.php index a811954c559..1a229c5d3b7 100644 --- a/lib/upgradelib.php +++ b/lib/upgradelib.php @@ -278,6 +278,11 @@ class core_upgrade_time { function upgrade_set_timeout($max_execution_time=300) { global $CFG; + // Support outageless upgrades. + if (defined('CLI_UPGRADE_RUNNING') && CLI_UPGRADE_RUNNING) { + return; + } + if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) { $upgraderunning = get_config(null, 'upgraderunning'); } else { @@ -1589,7 +1594,10 @@ function upgrade_started($preinstall=false) { ignore_user_abort(true); core_shutdown_manager::register_function('upgrade_finished_handler'); upgrade_setup_debug(true); - set_config('upgraderunning', time()+300); + // Support no-maintenance upgrades. + if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) { + set_config('upgraderunning', time() + 300); + } $started = true; } } @@ -1873,9 +1881,11 @@ function upgrade_core($version, $verbose) { require_once($CFG->libdir.'/db/upgrade.php'); // Defines upgrades try { - // Reset caches before any output. - cache_helper::purge_all(true); - purge_all_caches(); + // If we are in maintenance, we can purge all our caches here. + if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) { + cache_helper::purge_all(true); + purge_all_caches(); + } // Upgrade current language pack if we can upgrade_language_pack(); @@ -1910,10 +1920,12 @@ function upgrade_core($version, $verbose) { core_upgrade_time::record_detail('cache_helper::update_definitions'); // Purge caches again, just to be sure we arn't holding onto old stuff now. - cache_helper::purge_all(true); - core_upgrade_time::record_detail('cache_helper::purge_all'); - purge_all_caches(); - core_upgrade_time::record_detail('purge_all_caches'); + if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) { + cache_helper::purge_all(true); + core_upgrade_time::record_detail('cache_helper::purge_all'); + purge_all_caches(); + core_upgrade_time::record_detail('purge_all_caches'); + } // Clean up contexts - more and more stuff depends on existence of paths and contexts context_helper::cleanup_instances(); @@ -1950,9 +1962,11 @@ function upgrade_noncore($verbose) { // upgrade all plugins types try { - // Reset caches before any output. - cache_helper::purge_all(true); - purge_all_caches(); + // Reset caches before any output, unless we are not in maintenance. + if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) { + cache_helper::purge_all(true); + purge_all_caches(); + } $plugintypes = core_component::get_plugin_types(); upgrade_started(); @@ -1976,12 +1990,16 @@ function upgrade_noncore($verbose) { // Mark the site as upgraded. set_config('allversionshash', core_component::get_all_versions_hash()); core_upgrade_time::record_detail('core_component::get_all_versions_hash'); + set_config('allcomponenthash', core_component::get_all_component_hash()); + core_upgrade_time::record_detail('core_component::get_all_component_hash'); // Purge caches again, just to be sure we arn't holding onto old stuff now. - cache_helper::purge_all(true); - core_upgrade_time::record_detail('cache_helper::purge_all'); - purge_all_caches(); - core_upgrade_time::record_detail('purge_all_caches'); + if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) { + cache_helper::purge_all(true); + core_upgrade_time::record_detail('cache_helper::purge_all'); + purge_all_caches(); + core_upgrade_time::record_detail('purge_all_caches'); + } // Only display the final 'Success' if we also showed the heading. core_upgrade_time::record_end($CFG->debugdeveloper);