MDL-49329 admin: Add ability to cancel installation of a new plugin

The plugins check screen now provides buttons to cancel installation of
a plugin. Available only for new installations (not upgrades) and for
additional plugins (not standard), given that the web server process has
write access to the plugin folder.

This has also been reported as MDL-48535.

As a part of the patch, there is improved processing of page URLs during
the upgrade. All this dancing around $reload URL is not needed once the
$PAGE->url is properly set to guide the admin on the correct page during
the upgrade process.
This commit is contained in:
David Mudrák
2015-10-08 23:32:03 +02:00
parent f2d8ed45e3
commit 2f29cf6e63
6 changed files with 215 additions and 59 deletions
+45 -12
View File
@@ -106,6 +106,9 @@ $agreelicense = optional_param('agreelicense', 0, PARAM_BOOL);
$fetchupdates = optional_param('fetchupdates', 0, PARAM_BOOL);
$newaddonreq = optional_param('installaddonrequest', null, PARAM_RAW);
$upgradekeyhash = optional_param('upgradekeyhash', null, PARAM_ALPHANUM);
$installalldeps = optional_param('installalldeps', false, PARAM_BOOL);
$abortinstall = optional_param('abortinstall', null, PARAM_COMPONENT);
$abortinstallx = optional_param('abortinstallx', null, PARAM_BOOL);
// Set up PAGE.
$url = new moodle_url('/admin/index.php');
@@ -272,6 +275,12 @@ if ($CFG->version != $DB->get_field('config', 'value', array('name'=>'version'))
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.
@@ -327,6 +336,8 @@ if (!$cache and $version > $CFG->version) { // upgrade
die();
} else if (empty($confirmplugins)) {
// No sesskey support guaranteed here, because sessions might not work yet.
$strplugincheck = get_string('plugincheck');
$PAGE->navbar->add($strplugincheck);
@@ -334,20 +345,30 @@ if (!$cache and $version > $CFG->version) { // upgrade
$PAGE->set_heading($strplugincheck);
$PAGE->set_cacheable(false);
$reloadurl = new moodle_url($PAGE->url, array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0));
$pluginman = core_plugin_manager::instance();
if ($abortinstallx) {
$pluginman->cancel_all_plugin_installations();
redirect($PAGE->url);
}
if ($abortinstall) {
$pluginman->cancel_plugin_installation($abortinstall);
redirect($PAGE->url);
}
if ($fetchupdates) {
// No sesskey support guaranteed here, because sessions might not work yet.
$updateschecker = \core\update\checker::instance();
if ($updateschecker->enabled()) {
$updateschecker->fetch();
}
redirect($reloadurl);
redirect($PAGE->url);
}
$deployer = \core\update\deployer::instance();
if ($deployer->enabled()) {
$deployer->initialize($reloadurl, $reloadurl);
$deployer->initialize($PAGE->url, $PAGE->url);
$deploydata = $deployer->submitted_data();
if (!empty($deploydata)) {
@@ -358,16 +379,14 @@ if (!$cache and $version > $CFG->version) { // upgrade
}
echo $output->upgrade_plugin_check_page(core_plugin_manager::instance(), \core\update\checker::instance(),
$version, $showallplugins, $reloadurl, new moodle_url($PAGE->url, array(
'confirmupgrade' => 1, 'confirmrelease' => 1, 'confirmplugincheck' => 1, 'cache' => 0)));
$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)) {
$reloadurl = new moodle_url($PAGE->url, array('confirmupgrade' => 1, 'confirmrelease' => 1, 'cache' => 0));
echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url);
die();
}
unset($failed);
@@ -391,12 +410,15 @@ if (!$cache and $branch <> $CFG->branch) { // Update the branch
if (!$cache and moodle_needs_upgrading()) {
$PAGE->set_url(new moodle_url($PAGE->url, array('confirmplugincheck' => $confirmplugins)));
check_upgrade_key($upgradekeyhash);
if (!$PAGE->headerprinted) {
// means core upgrade or installation was not already done
if (!$confirmplugins) {
$pluginman = core_plugin_manager::instance();
$strplugincheck = get_string('plugincheck');
$PAGE->navbar->add($strplugincheck);
@@ -404,6 +426,18 @@ if (!$cache and moodle_needs_upgrading()) {
$PAGE->set_heading($strplugincheck);
$PAGE->set_cacheable(false);
if ($abortinstallx) {
require_sesskey();
$pluginman->cancel_all_plugin_installations();
redirect($PAGE->url);
}
if ($abortinstall) {
require_sesskey();
$pluginman->cancel_plugin_installation($abortinstall);
redirect($PAGE->url);
}
if ($fetchupdates) {
require_sesskey();
$updateschecker = \core\update\checker::instance();
@@ -429,7 +463,7 @@ if (!$cache and moodle_needs_upgrading()) {
}
// Show plugins info.
echo $output->upgrade_plugin_check_page(core_plugin_manager::instance(), \core\update\checker::instance(),
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)));
@@ -438,11 +472,10 @@ if (!$cache and moodle_needs_upgrading()) {
// Make sure plugin dependencies are always checked.
$failed = array();
if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
if (!$pluginman->all_plugins_ok($version, $failed)) {
/** @var core_admin_renderer $output */
$output = $PAGE->get_renderer('core', 'admin');
$reloadurl = new moodle_url($PAGE->url, array('cache' => 0));
echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url);
die();
}
unset($failed);
+52 -13
View File
@@ -206,11 +206,15 @@ class core_admin_renderer extends plugin_renderer_base {
$output = '';
$output .= $this->header();
$output .= $this->box_start('generalbox');
$output .= html_writer::tag('p', get_string('pluginchecknotice', 'core_plugin'));
$output .= $this->box_start('generalbox', 'plugins-check-page');
$output .= html_writer::tag('p', get_string('pluginchecknotice', 'core_plugin'), array('class' => 'page-description'));
if ($checker->enabled()) {
$output .= $this->container_start('checkforupdates');
$output .= $this->single_button(new moodle_url($reloadurl, array('fetchupdates' => 1)), get_string('checkforupdates', 'core_plugin'));
$output .= $this->single_button(
new moodle_url($reloadurl, array('fetchupdates' => 1)),
get_string('checkforupdates', 'core_plugin')
);
if ($timefetched = $checker->get_last_timefetched()) {
$output .= $this->container(get_string('checkforupdateslast', 'core_plugin',
userdate($timefetched, get_string('strftimedatetime', 'core_langconfig'))));
@@ -877,7 +881,13 @@ class core_admin_renderer extends plugin_renderer_base {
);
$table->data = array();
$numofhighlighted = array(); // number of highlighted rows per this subsection
// Number of displayed plugins per type (we call them 'highlighted'
// because in early version of the page, we always displayed all the
// plugins rows and highlighted were those requiring attention.
$numofhighlighted = array();
// List of all components we can cancel installation of.
$installabortable = array();
foreach ($plugininfo as $type => $plugins) {
@@ -954,6 +964,18 @@ 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;
$status .= $this->output->single_button(
new moodle_url($this->page->url, array('abortinstall' => $plugin->component)),
get_string('cancelinstallone', 'core_plugin'),
'post',
array('class' => 'actionbutton')
);
}
}
$availableupdates = $plugin->available_updates();
if (!empty($availableupdates)) {
foreach ($availableupdates as $availableupdate) {
@@ -1014,19 +1036,36 @@ class core_admin_renderer extends plugin_renderer_base {
$out .= $this->output->container_end();
} else {
$out = $this->output->container_start('somehighlighted', 'plugins-check-info');
$out = $this->output->container_start('somehighlighted', 'plugins-check-info');
if (empty($options['full'])) {
$out .= $this->output->heading(get_string('somehighlighted', 'core_plugin', $sumofhighlighted));
$out .= html_writer::link(new moodle_url($this->page->url,
array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1, 'cache' => 0)),
get_string('somehighlightedinfo', 'core_plugin'));
} else {
$out .= $this->output->heading(get_string('somehighlightedall', 'core_plugin', $sumofhighlighted));
$out .= html_writer::link(new moodle_url($this->page->url,
array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 0, 'cache' => 0)),
get_string('somehighlightedonly', 'core_plugin'));
}
$out .= $this->output->container_end();
$out .= $this->output->container_start('actions');
if ($installabortable) {
$out .= $this->output->single_button(
new moodle_url($this->page->url, array('abortinstallx' => 1)),
get_string('cancelinstallall', 'core_plugin', count($installabortable)),
'post',
array('class' => 'actionbutton')
);
}
if (empty($options['full'])) {
$out .= html_writer::link(new moodle_url($this->page->url, array('confirmupgrade' => 1, 'confirmrelease' => 1,
'cache' => 0, 'showallplugins' => 1)), get_string('somehighlightedinfo', 'core_plugin'));
} else {
$out .= html_writer::link(
new moodle_url($this->page->url, array('confirmupgrade' => 1, 'confirmrelease' => 1,
'cache' => 0, 'showallplugins' => 0)), get_string('somehighlightedonly', 'core_plugin'));
}
$out .= $this->output->container_end(); // .actions
$out .= $this->output->container_end(); // #plugins-check-info .somehighlighted
}
if ($sumofhighlighted > 0 or $options['full']) {
@@ -1104,7 +1143,7 @@ class core_admin_renderer extends plugin_renderer_base {
// TODO implement the button functionality.
$out .= html_writer::link(
new moodle_url('/admin/tool/installaddon/'),
new moodle_url($this->page->url, array('installalldeps' => 1, 'sesskey' => sesskey())),
get_string('dependencyinstallmissing', 'core_plugin'),
array('class' => 'btn')
);
+2
View File
@@ -27,6 +27,8 @@ defined('MOODLE_INTERNAL') || die();
$string['actions'] = 'Actions';
$string['availability'] = 'Availability';
$string['cancelinstallall'] = 'Cancel new installations ({$a})';
$string['cancelinstallone'] = 'Cancel installation';
$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}"';
+37
View File
@@ -1531,6 +1531,43 @@ class core_plugin_manager {
}
}
/**
* Removes the plugin code directory if it is not installed yet.
*
* This is intended for the plugins check screen to give the admin a chance
* to cancel the installation of just unzipped plugin before the database
* upgrade happens.
*
* @param string $component
* @return bool
*/
public function cancel_plugin_installation($component) {
$plugin = $this->get_plugin_info($component);
if (empty($plugin) or $plugin->is_standard() or $plugin->get_status() !== self::PLUGIN_STATUS_NEW
or !$this->is_plugin_folder_removable($plugin->component)) {
return false;
}
return remove_dir($plugin->rootdir);
}
/**
* Cancels installation of all new additional plugins.
*/
public function cancel_all_plugin_installations() {
foreach ($this->get_plugins() as $type => $plugins) {
foreach ($plugins as $plugin) {
if (!$plugin->is_standard() and $plugin->get_status() === self::PLUGIN_STATUS_NEW
and $this->is_plugin_folder_removable($plugin->component)) {
$this->cancel_plugin_installation($plugin->component);
}
}
}
}
/**
* Reorders plugin types into a sequence to be displayed
*
+78 -33
View File
@@ -532,7 +532,7 @@ img.iconsmall {
text-align: right
}
// Plugins check.
// Plugins overview control panel.
#page-admin-index #notice .checkforupdates {
text-align: center;
}
@@ -583,37 +583,86 @@ img.iconsmall {
background-color: @inputDisabledBackground;
}
#plugins-check .requires-ok {
.muted
}
// Plugins check page displayed during upgrade.
#plugins-check-page {
#plugins-check .displayname {
.pluginicon {
margin-right: 5px;
width: 16px;
}
.plugindir {
.muted;
font-size: @fontSizeSmall;
}
}
#plugins-check .requires ul {
margin-left: 13px; // To undo the default 25px, does not look that great inside a table cell;
}
#plugins-check .status .pluginupdateinfo {
padding: 5px 10px;
margin: 10px 0px;
background-color: @infoBackground;
.border-radius(10px);
}
#plugins-check-available-dependencies {
.displayname .component {
font-size: @fontSizeSmall;
.page-description {
.muted
}
// Check for updates.
.checkforupdates {
.singlebutton {
margin: 5px 0;
padding: 0;
input {
margin: 0;
}
}
}
// Section before the plugins check table.
#plugins-check-info {
.actions {
.actionbutton {
display: inline-block;
margin: 5px;
padding: 0;
input {
margin: 0;
}
}
}
}
// Plugins check table.
#plugins-check {
.requires-ok {
.muted
}
.displayname {
.pluginicon {
margin-right: 5px;
width: 16px;
}
.plugindir {
.muted;
font-size: @fontSizeSmall;
}
}
.requires ul {
margin-left: 13px; // To undo the default 25px, does not look that great inside a table cell;
}
.status {
.actionbutton {
margin: 5px 0px;
padding: 0;
input {
margin: 0;
}
}
.pluginupdateinfo {
padding: 5px 10px;
margin: 10px 0px;
background-color: @infoBackground;
.border-radius(10px);
.separator:after {
content: " | ";
}
}
}
}
// Available dependencies on the plugins check page.
#plugins-check-available-dependencies {
.displayname .component {
font-size: @fontSizeSmall;
.muted
}
}
}
#page-admin-index .upgradepluginsinfo {
@@ -645,10 +694,6 @@ img.iconsmall {
border-left: 1px dotted @grayLight;
}
#plugins-check .status .pluginupdateinfo .separator:after {
content: " | ";
}
#plugins-control-panel .msg td {
text-align: center;
}
File diff suppressed because one or more lines are too long