MDL-49329 admin: Add ability to cancel upgrade of the plugin

If there is an available archived zip with the version of the plugin
currently installed, we can use it to cancel/abort the upgrade of the
plugin. This is internally handled as the installation of the archived
zip and goes through all the validation and confirmation.

Additionally, some other parts were improved. Most notably, renderer no
longer decides itself if some installation can be cancelled but it
always asks the controller (plugin manager).

The button for installation was moved to the left so there should be
first buttons to add things, and then buttons to cancel things (which is
common in normal forms).
This commit is contained in:
David Mudrák
2015-10-09 18:07:28 +02:00
parent 80c3c6501d
commit c20e9ae836
7 changed files with 217 additions and 76 deletions

View File

@@ -112,6 +112,9 @@ $confirminstalldep = optional_param('confirminstalldep', false, PARAM_BOOL); //
$abortinstall = optional_param('abortinstall', null, PARAM_COMPONENT); // Cancel installation of the given new plugin.
$abortinstallx = optional_param('abortinstallx', null, PARAM_BOOL); // Cancel installation of all new plugins.
$confirmabortinstall = optional_param('confirmabortinstall', false, PARAM_BOOL); // Installation cancel confirmed.
$abortupgrade = optional_param('abortupgrade', null, PARAM_COMPONENT); // Cancel upgrade of the given existing plugin.
$abortupgradex = optional_param('abortupgradex', null, PARAM_BOOL); // Cancel upgrade of all upgradable plugins.
$confirmabortupgrade = optional_param('confirmabortupgrade', false, PARAM_BOOL); // Upgrade cancel confirmed.
$installupdate = optional_param('installupdate', null, PARAM_COMPONENT); // Install given available update.
$installupdateversion = optional_param('installupdateversion', null, PARAM_INT); // Version of the available update to install.
$installupdatex = optional_param('installupdatex', false, PARAM_BOOL); // Install all available plugin updates.
@@ -352,18 +355,34 @@ if (!$cache and $version > $CFG->version) { // upgrade
$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);
}
// 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) {
$pluginman->cancel_all_plugin_installations();
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(true, $continue);
echo $output->upgrade_confirm_abort_install_page($abortables, $continue);
die();
}
}
redirect($PAGE->url);
}
// Cancel single plugin installation.
if ($abortinstall) {
@@ -373,9 +392,40 @@ if (!$cache and $version > $CFG->version) { // upgrade
redirect($PAGE->url);
} else {
$continue = new moodle_url($PAGE->url, array('abortinstall' => $abortinstall, 'confirmabortinstall' => 1));
echo $output->upgrade_confirm_abort_install_page($abortinstall, $continue);
$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) {
// 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.
@@ -425,15 +475,6 @@ if (!$cache and $version > $CFG->version) { // upgrade
}
}
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);
}
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();
@@ -487,18 +528,34 @@ if (!$cache and moodle_needs_upgrading()) {
$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) {
$pluginman->cancel_all_plugin_installations();
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(true, $continue);
echo $output->upgrade_confirm_abort_install_page($abortables, $continue);
die();
}
}
redirect($PAGE->url);
}
// Cancel single plugin installation.
if ($abortinstall) {
@@ -508,16 +565,38 @@ if (!$cache and moodle_needs_upgrading()) {
redirect($PAGE->url);
} else {
$continue = new moodle_url($PAGE->url, array('abortinstall' => $abortinstall, 'confirmabortinstall' => 1));
echo $output->upgrade_confirm_abort_install_page($abortinstall, $continue);
$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);
}
}
if ($fetchupdates) {
// Cancel all plugins upgrades (that is, restore archived versions).
if ($abortupgradex) {
require_sesskey();
$updateschecker = \core\update\checker::instance();
if ($updateschecker->enabled()) {
$updateschecker->fetch();
$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);
}

View File

@@ -231,30 +231,13 @@ class core_admin_renderer extends plugin_renderer_base {
/**
* Display a page to confirm plugin installation cancelation.
*
* @param bool|string $plugin true if cancelling all, component name otherwsie
* @param array $abortable list of \core\update\plugininfo
* @param moodle_url $continue
* @return string
*/
public function upgrade_confirm_abort_install_page($plugin, moodle_url $continue) {
public function upgrade_confirm_abort_install_page(array $abortable, moodle_url $continue) {
$pluginman = core_plugin_manager::instance();
$abortable = array();
if ($plugin === true) {
foreach ($pluginman->get_plugins() as $type => $pluginfos) {
foreach ($pluginfos as $pluginfo) {
if ($pluginman->can_cancel_plugin_installation($pluginfo)) {
$abortable[] = $pluginfo;
}
}
}
} else {
$pluginfo = $pluginman->get_plugin_info($plugin);
if ($pluginman->can_cancel_plugin_installation($pluginfo)) {
$abortable[] = $pluginfo;
}
}
if (empty($abortable)) {
// The UI should not allow this.
@@ -885,7 +868,9 @@ class core_admin_renderer extends plugin_renderer_base {
// Number of plugins requiring attention.
$sumattention = 0;
// List of all components we can cancel installation of.
$installabortable = array();
$installabortable = $pluginman->list_cancellable_installations();
// List of all components we can cancel upgrade of.
$upgradeabortable = $pluginman->list_restorable_archives();
foreach ($plugininfo as $type => $plugins) {
@@ -963,16 +948,22 @@ class core_admin_renderer extends plugin_renderer_base {
}
$status = html_writer::span(get_string('status_' . $statuscode, 'core_plugin'), $statusclass);
if ($statuscode == core_plugin_manager::PLUGIN_STATUS_NEW and !$plugin->is_standard()) {
if ($pluginman->is_plugin_folder_removable($plugin->component)) {
$installabortable[] = $plugin->component;
if (!empty($installabortable[$plugin->component])) {
$status .= $this->output->single_button(
new moodle_url($this->page->url, array('abortinstall' => $plugin->component)),
get_string('cancelinstallone', 'core_plugin'),
'post',
array('class' => 'actionbutton')
array('class' => 'actionbutton cancelinstallone')
);
}
if (!empty($upgradeabortable[$plugin->component])) {
$status .= $this->output->single_button(
new moodle_url($this->page->url, array('abortupgrade' => $plugin->component)),
get_string('cancelupgradeone', 'core_plugin'),
'post',
array('class' => 'actionbutton cancelupgradeone')
);
}
$availableupdates = $plugin->available_updates();
@@ -1042,6 +1033,17 @@ class core_admin_renderer extends plugin_renderer_base {
}
$out .= $this->output->container_start('actions');
$installableupdates = $pluginman->filter_installable($pluginman->available_updates());
if ($installableupdates) {
$out .= $this->output->single_button(
new moodle_url($this->page->url, array('installupdatex' => 1)),
get_string('updateavailableinstallall', 'core_admin', count($installableupdates)),
'post',
array('class' => 'singlebutton updateavailableinstallall')
);
}
if ($installabortable) {
$out .= $this->output->single_button(
new moodle_url($this->page->url, array('abortinstallx' => 1)),
@@ -1051,13 +1053,12 @@ class core_admin_renderer extends plugin_renderer_base {
);
}
$installableupdates = $pluginman->filter_installable($pluginman->available_updates());
if ($installableupdates) {
if ($upgradeabortable) {
$out .= $this->output->single_button(
new moodle_url($this->page->url, array('installupdatex' => 1)),
get_string('updateavailableinstallall', 'core_admin', count($installableupdates)),
new moodle_url($this->page->url, array('abortupgradex' => 1)),
get_string('cancelupgradeall', 'core_plugin', count($upgradeabortable)),
'post',
array('class' => 'singlebutton updateavailableinstallall')
array('class' => 'singlebutton cancelupgradeall')
);
}
@@ -1622,7 +1623,6 @@ class core_admin_renderer extends plugin_renderer_base {
if ($plugin->is_standard()) {
$row->attributes['class'] .= ' standard';
//$source = html_writer::div(get_string('sourcestd', 'core_plugin'), 'source label');
$source = '';
} else {
$row->attributes['class'] .= ' extension';

View File

@@ -1083,7 +1083,7 @@ $string['updateavailableforplugin'] = 'There is a newer version for some of your
$string['updateavailable_moreinfo'] = 'More info...';
$string['updateavailable_release'] = 'Moodle {$a}';
$string['updateavailable_version'] = 'Version {$a}';
$string['updateavailableinstall'] = 'Install';
$string['updateavailableinstall'] = 'Install this update';
$string['updateavailableinstallall'] = 'Install available updates ({$a})';
$string['updateavailableinstallallhead'] = 'Installing available updates';
$string['updateavailablenot'] = 'Your Moodle code is up-to-date!';

View File

@@ -28,10 +28,13 @@ defined('MOODLE_INTERNAL') || die();
$string['actions'] = 'Actions';
$string['availability'] = 'Availability';
$string['cancelinstallall'] = 'Cancel new installations ({$a})';
$string['cancelinstallone'] = 'Cancel installation';
$string['cancelinstallone'] = 'Cancel this installation';
$string['cancelinstallhead'] = 'Cancelling installation of plugins';
$string['cancelinstallinfo'] = 'Following plugins are not fully installed yet and their installation can be cancelled. To do so, the plugin folder must be removed from your server now. Make sure that is really what you want to prevent accidental data loss (such as your own code modifications).';
$string['cancelinstallinfodir'] = 'Folder to be deleted: {$a}';
$string['cancelupgradeall'] = 'Cancel upgrades ({$a})';
$string['cancelupgradehead'] = 'Restoring previous version of plugins';
$string['cancelupgradeone'] = 'Cancel this upgrade';
$string['checkforupdates'] = 'Check for available updates';
$string['checkforupdateslast'] = 'Last check done on {$a}';
$string['detectedmisplacedplugin'] = 'Plugin "{$a->component}" is installed in incorrect location "{$a->current}", expected location is "{$a->expected}"';

View File

@@ -1941,12 +1941,17 @@ class core_plugin_manager {
/**
* Can the installation of the new plugin be cancelled?
*
* Subplugins can be cancelled only via their parent plugin, not separately
* (they are considered as implicit requirements if distributed together
* with the main package).
*
* @param \core\plugininfo\base $plugin
* @return bool
*/
public function can_cancel_plugin_installation(\core\plugininfo\base $plugin) {
if (empty($plugin) or $plugin->is_standard() or !$this->is_plugin_folder_removable($plugin->component)) {
if (empty($plugin) or $plugin->is_standard() or $plugin->is_subplugin()
or !$this->is_plugin_folder_removable($plugin->component)) {
return false;
}
@@ -1957,6 +1962,32 @@ class core_plugin_manager {
return false;
}
/**
* Can the upgrade of the existing plugin be cancelled?
*
* Subplugins can be cancelled only via their parent plugin, not separately
* (they are considered as implicit requirements if distributed together
* with the main package).
*
* @param \core\plugininfo\base $plugin
* @return bool
*/
public function can_cancel_plugin_upgrade(\core\plugininfo\base $plugin) {
if (empty($plugin) or $plugin->is_standard() or $plugin->is_subplugin()
or !$this->is_plugin_folder_removable($plugin->component)) {
return false;
}
if ($plugin->get_status() === self::PLUGIN_STATUS_UPGRADE) {
if ($this->get_code_manager()->get_archived_plugin_version($plugin->component, $plugin->versiondb)) {
return true;
}
}
return false;
}
/**
* Removes the plugin code directory if it is not installed yet.
*
@@ -1978,17 +2009,22 @@ class core_plugin_manager {
}
/**
* Cancels installation of all new additional plugins.
* Returns plugins, the installation of which can be cancelled.
*
* @return array [(string)component] => (\core\plugininfo\base)plugin
*/
public function cancel_all_plugin_installations() {
public function list_cancellable_installations() {
$cancellable = array();
foreach ($this->get_plugins() as $type => $plugins) {
foreach ($plugins as $plugin) {
if ($this->can_cancel_plugin_installation($plugin)) {
$this->cancel_plugin_installation($plugin->component);
$cancellable[$plugin->component] = $plugin;
}
}
}
return $cancellable;
}
/**
@@ -2001,6 +2037,29 @@ class core_plugin_manager {
return $this->get_code_manager()->archive_plugin_version($plugin->rootdir, $plugin->component, $plugin->versiondisk);
}
/**
* Returns list of all archives that can be installed to cancel the plugin upgrade.
*
* @return array [(string)component] => {(string)->component, (string)->zipfilepath}
*/
public function list_restorable_archives() {
$codeman = $this->get_code_manager();
$restorable = array();
foreach ($this->get_plugins() as $type => $plugins) {
foreach ($plugins as $plugin) {
if ($this->can_cancel_plugin_upgrade($plugin)) {
$restorable[$plugin->component] = (object)array(
'component' => $plugin->component,
'zipfilepath' => $codeman->get_archived_plugin_version($plugin->component, $plugin->versiondb)
);
}
}
}
return $restorable;
}
/**
* Reorders plugin types into a sequence to be displayed
*

View File

@@ -646,7 +646,7 @@ img.iconsmall {
margin-right: 1em;
}
.singlebutton {
margin: 5px;
margin: 5px 0;
padding: 0;
div, input {
margin: 0 3px 0 0;

File diff suppressed because one or more lines are too long