Commit 2d00be61 authored by David Mudrák's avatar David Mudrák
Browse files

MDL-49329 admin: Make plugin manager able to install from local zips too

The plugin manager's method install_remote_plugins() has been changed to
install_plugins() and it is now able to install plugins from the
provided list of locally available ZIP files, too. This is used by the
Install plugins admin tool.
parent da54cf11
......@@ -368,7 +368,7 @@ if (!$cache and $version > $CFG->version) { // upgrade
if ($installdepx) {
// No sesskey support guaranteed here, because sessions might not work yet.
$installable = $pluginman->filter_installable($pluginman->missing_dependencies(true));
upgrade_install_remote_plugins($installable, $confirminstalldep,
upgrade_install_plugins($installable, $confirminstalldep,
get_string('dependencyinstallhead', 'core_plugin'),
new moodle_url($PAGE->url, array('installdepx' => 1, 'confirminstalldep' => 1))
);
......@@ -380,7 +380,7 @@ if (!$cache and $version > $CFG->version) { // upgrade
$installable = $pluginman->filter_installable($pluginman->missing_dependencies(true));
if (!empty($installable[$installdep])) {
$installable = array($installable[$installdep]);
upgrade_install_remote_plugins($installable, $confirminstalldep,
upgrade_install_plugins($installable, $confirminstalldep,
get_string('dependencyinstallhead', 'core_plugin'),
new moodle_url($PAGE->url, array('installdep' => $installdep, 'confirminstalldep' => 1))
);
......@@ -391,7 +391,7 @@ if (!$cache and $version > $CFG->version) { // upgrade
if ($installupdatex) {
// No sesskey support guaranteed here, because sessions might not work yet.
$installable = $pluginman->filter_installable($pluginman->available_updates());
upgrade_install_remote_plugins($installable, $confirminstallupdate,
upgrade_install_plugins($installable, $confirminstallupdate,
get_string('updateavailableinstallallhead', 'core_admin'),
new moodle_url($PAGE->url, array('installupdatex' => 1, 'confirminstallupdate' => 1))
);
......@@ -402,7 +402,7 @@ if (!$cache and $version > $CFG->version) { // upgrade
// 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_remote_plugins($installable, $confirminstallupdate,
upgrade_install_plugins($installable, $confirminstallupdate,
get_string('updateavailableinstallallhead', 'core_admin'),
new moodle_url($PAGE->url, array('installupdate' => $installupdate,
'installupdateversion' => $installupdateversion, 'confirminstallupdate' => 1)
......@@ -493,7 +493,7 @@ if (!$cache and moodle_needs_upgrading()) {
if ($installdepx) {
require_sesskey();
$installable = $pluginman->filter_installable($pluginman->missing_dependencies(true));
upgrade_install_remote_plugins($installable, $confirminstalldep,
upgrade_install_plugins($installable, $confirminstalldep,
get_string('dependencyinstallhead', 'core_plugin'),
new moodle_url($PAGE->url, array('installdepx' => 1, 'confirminstalldep' => 1))
);
......@@ -505,7 +505,7 @@ if (!$cache and moodle_needs_upgrading()) {
$installable = $pluginman->filter_installable($pluginman->missing_dependencies(true));
if (!empty($installable[$installdep])) {
$installable = array($installable[$installdep]);
upgrade_install_remote_plugins($installable, $confirminstalldep,
upgrade_install_plugins($installable, $confirminstalldep,
get_string('dependencyinstallhead', 'core_plugin'),
new moodle_url($PAGE->url, array('installdep' => $installdep, 'confirminstalldep' => 1))
);
......@@ -516,7 +516,7 @@ if (!$cache and moodle_needs_upgrading()) {
if ($installupdatex) {
require_sesskey();
$installable = $pluginman->filter_installable($pluginman->available_updates());
upgrade_install_remote_plugins($installable, $confirminstallupdate,
upgrade_install_plugins($installable, $confirminstallupdate,
get_string('updateavailableinstallallhead', 'core_admin'),
new moodle_url($PAGE->url, array('installupdatex' => 1, 'confirminstallupdate' => 1))
);
......@@ -527,7 +527,7 @@ if (!$cache and moodle_needs_upgrading()) {
require_sesskey();
if ($pluginman->is_remote_plugin_installable($installupdate, $installupdateversion)) {
$installable = array($pluginman->get_remote_plugin_info($installupdate, $installupdateversion, true));
upgrade_install_remote_plugins($installable, $confirminstallupdate,
upgrade_install_plugins($installable, $confirminstallupdate,
get_string('updateavailableinstallallhead', 'core_admin'),
new moodle_url($PAGE->url, array('installupdate' => $installupdate,
'installupdateversion' => $installupdateversion, 'confirminstallupdate' => 1)
......
......@@ -183,7 +183,7 @@ if ($installupdatex) {
$PAGE->set_popup_notification_allowed(false);
$installable = $pluginman->filter_installable($pluginman->available_updates());
upgrade_install_remote_plugins($installable, $confirminstallupdate,
upgrade_install_plugins($installable, $confirminstallupdate,
get_string('updateavailableinstallallhead', 'core_admin'),
new moodle_url($PAGE->url, array('installupdatex' => 1, 'confirminstallupdate' => 1))
);
......@@ -201,7 +201,7 @@ if ($installupdate and $installupdateversion) {
if ($pluginman->is_remote_plugin_installable($installupdate, $installupdateversion)) {
$installable = array($pluginman->get_remote_plugin_info($installupdate, $installupdateversion, true));
upgrade_install_remote_plugins($installable, $confirminstallupdate,
upgrade_install_plugins($installable, $confirminstallupdate,
get_string('updateavailableinstallallhead', 'core_admin'),
new moodle_url($PAGE->url, array('installupdate' => $installupdate,
'installupdateversion' => $installupdateversion, 'confirminstallupdate' => 1)
......
......@@ -1033,7 +1033,7 @@ class core_admin_renderer extends plugin_renderer_base {
* @param moodle_url $cancel URL for the cancel link, defaults to the current page
* @return string HTML
*/
public function install_remote_plugins_buttons(moodle_url $continue=null, $label=null, moodle_url $cancel=null) {
public function install_plugins_buttons(moodle_url $continue=null, $label=null, moodle_url $cancel=null) {
$out = html_writer::start_div('install-remote-plugins-buttons');
......
......@@ -68,9 +68,9 @@ $string['overviewall'] = 'All plugins';
$string['overviewext'] = 'Additional plugins';
$string['overviewupdatable'] = 'Available updates';
$string['packagesdebug'] = 'Debugging output enabled';
$string['packagesdownloading'] = 'Downloading packages';
$string['packagesextracting'] = 'Extracting packages';
$string['packagesvalidating'] = 'Validating packages';
$string['packagesdownloading'] = 'Downloading {$a}';
$string['packagesextracting'] = 'Extracting {$a}';
$string['packagesvalidating'] = 'Validating {$a}';
$string['packagesvalidatingfailed'] = 'Installation aborted due to validation failure';
$string['packagesvalidatingok'] = 'Validation successful, installation can continue';
$string['plugincheckall'] = 'All plugins';
......
......@@ -1092,6 +1092,20 @@ class core_plugin_manager {
return $this->get_code_manager()->unzip_plugin_file($zipfilepath, $targetdir, $rootdir);
}
/**
* Detects the plugin's name from its ZIP file.
*
* Plugin ZIP packages are expected to contain a single directory and the
* directory name would become the plugin name once extracted to the Moodle
* dirroot.
*
* @param string $zipfilepath full path to the ZIP files
* @return string|bool false on error
*/
public function get_plugin_zip_root_dir($zipfilepath) {
return $this->get_code_manager()->get_plugin_zip_root_dir($zipfilepath);
}
/**
* Return a list of missing dependencies.
*
......@@ -1199,18 +1213,26 @@ class core_plugin_manager {
}
/**
* Perform the installation of plugins available in the plugins directory.
* Perform the installation of plugins.
*
* If used for installation of remote plugins from the Moodle Plugins
* directory, the $plugins must be list of {@link \core\update\remote_info}
* object that represent installable remote plugins. The caller can use
* {@link self::filter_installable()} to prepare the list.
*
* The list of plugins is supposed to be processed by
* {@link self::filter_installable()} to make sure all the plugins are
* valid.
* If used for installation of plugins from locally available ZIP files,
* the $plugins should be list of objects with properties ->component and
* ->zipfilepath.
*
* @param array $plugins list of installable remote plugins
* The method uses {@link mtrace()} to produce direct output and can be
* used in both web and cli interfaces.
*
* @param array $plugins list of plugins
* @param bool $confirmed should the files be really deployed into the dirroot?
* @param bool $silent perform without output
* @return bool true on success
*/
public function install_remote_plugins(array $plugins, $confirmed, $silent) {
public function install_plugins(array $plugins, $confirmed, $silent) {
global $CFG, $OUTPUT;
if (empty($plugins)) {
......@@ -1223,24 +1245,32 @@ class core_plugin_manager {
$silent or $this->mtrace(get_string('packagesdebug', 'core_plugin'), PHP_EOL, DEBUG_NORMAL);
// Download all ZIP packages if we do not have them yet.
$silent or $this->mtrace(get_string('packagesdownloading', 'core_plugin'), ' ... ');
$zips = array();
foreach ($plugins as $plugin) {
$zips[$plugin->component] = $this->get_remote_plugin_zip($plugin->version->downloadurl, $plugin->version->downloadmd5);
$silent or $this->mtrace(PHP_EOL.$plugin->version->downloadurl, '', DEBUG_DEVELOPER);
$silent or $this->mtrace(PHP_EOL.' -> '.$zips[$plugin->component], ' ... ', DEBUG_DEVELOPER);
if (!$zips[$plugin->component]) {
$silent or $this->mtrace(get_string('error'));
return false;
if ($plugin instanceof \core\update\remote_info) {
$zips[$plugin->component] = $this->get_remote_plugin_zip($plugin->version->downloadurl,
$plugin->version->downloadmd5);
$silent or $this->mtrace(get_string('packagesdownloading', 'core_plugin', $plugin->component), ' ... ');
$silent or $this->mtrace(PHP_EOL.' <- '.$plugin->version->downloadurl, '', DEBUG_DEVELOPER);
$silent or $this->mtrace(PHP_EOL.' -> '.$zips[$plugin->component], ' ... ', DEBUG_DEVELOPER);
if (!$zips[$plugin->component]) {
$silent or $this->mtrace(get_string('error'));
return false;
}
$silent or $this->mtrace($ok);
} else {
if (empty($plugin->zipfilepath)) {
throw new coding_exception('Unexpected data structure provided');
}
$zips[$plugin->component] = $plugin->zipfilepath;
$silent or $this->mtrace('ZIP '.$plugin->zipfilepath, PHP_EOL, DEBUG_DEVELOPER);
}
}
$silent or $this->mtrace($ok);
// Validate all downloaded packages.
$silent or $this->mtrace(get_string('packagesvalidating', 'core_plugin'), ' ... '.PHP_EOL);
foreach ($plugins as $plugin) {
$zipfile = $zips[$plugin->component];
$silent or $this->mtrace('* '.s($plugin->name). ' ('.$plugin->component.')', ' ... ');
$silent or $this->mtrace(get_string('packagesvalidating', 'core_plugin', $plugin->component), ' ... ');
list($plugintype, $pluginname) = core_component::normalize_component($plugin->component);
$tmp = make_request_directory();
$zipcontents = $this->unzip_plugin_file($zipfile, $tmp, $pluginname);
......@@ -1305,9 +1335,8 @@ class core_plugin_manager {
}
// Extract all ZIP packs do the dirroot.
$silent or $this->mtrace(get_string('packagesextracting', 'core_plugin'), ' ... '.PHP_EOL);
foreach ($plugins as $plugin) {
$silent or $this->mtrace('* '.s($plugin->name). ' ('.$plugin->component.')', ' ... ');
$silent or $this->mtrace(get_string('packagesextracting', 'core_plugin', $plugin->component), ' ... ');
$zipfile = $zips[$plugin->component];
list($plugintype, $pluginname) = core_component::normalize_component($plugin->component);
$target = $this->get_plugintype_root($plugintype);
......
......@@ -220,6 +220,46 @@ class code_manager {
return $files;
}
/**
* Detects the plugin's name from its ZIP file.
*
* Plugin ZIP packages are expected to contain a single directory and the
* directory name would become the plugin name once extracted to the Moodle
* dirroot.
*
* @param string $zipfilepath full path to the ZIP files
* @return string|bool false on error
*/
public function get_plugin_zip_root_dir($zipfilepath) {
$fp = get_file_packer('application/zip');
$files = $fp->list_files($zipfilepath);
if (empty($files)) {
return false;
}
$rootdirname = null;
foreach ($files as $file) {
$pathnameitems = explode('/', $file->pathname);
if (empty($pathnameitems)) {
return false;
}
// Set the expected name of the root directory in the first
// iteration of the loop.
if ($rootdirname === null) {
$rootdirname = $pathnameitems[0];
}
// Require the same root directory for all files in the ZIP
// package.
if ($rootdirname !== $pathnameitems[0]) {
return false;
}
}
return $rootdirname;
}
// This is the end, my only friend, the end ... of external public API.
/**
......
......@@ -159,4 +159,15 @@ class core_update_code_manager_testcase extends advanced_testcase {
$zipfilepath = __DIR__.'/fixtures/update_validator/zips/bar.zip';
$files = $codeman->unzip_plugin_file($zipfilepath, $targetdir, 'bar');
}
public function test_get_plugin_zip_root_dir() {
$codeman = new \core\update\testable_code_manager();
$zipfilepath = __DIR__.'/fixtures/update_validator/zips/invalidroot.zip';
$this->assertEquals('invalid-root', $codeman->get_plugin_zip_root_dir($zipfilepath));
$zipfilepath = __DIR__.'/fixtures/update_validator/zips/bar.zip';
$this->assertEquals('bar', $codeman->get_plugin_zip_root_dir($zipfilepath));
}
}
......@@ -2383,7 +2383,7 @@ function check_upgrade_key($upgradekeyhash) {
* @param moodle_url|string|null $continue URL to proceed with installation at the validation screen
* @param moodle_url|string|null $return URL to go back on cancelling at the validation screen
*/
function upgrade_install_remote_plugins(array $installable, $confirmed, $heading='', $continue=null, $return=null) {
function upgrade_install_plugins(array $installable, $confirmed, $heading='', $continue=null, $return=null) {
global $PAGE;
if (empty($return)) {
......@@ -2398,8 +2398,8 @@ function upgrade_install_remote_plugins(array $installable, $confirmed, $heading
if ($confirmed) {
// Installation confirmed at the validation results page.
if (!$pluginman->install_remote_plugins($installable, true, true)) {
throw new moodle_exception('install_remote_plugins_failed', 'core_plugin', $return);
if (!$pluginman->install_plugins($installable, true, true)) {
throw new moodle_exception('install_plugins_failed', 'core_plugin', $return);
}
// Always redirect to admin/index.php to perform the database upgrade.
redirect(new moodle_url('/admin/index.php?cache=0'));
......@@ -2411,12 +2411,12 @@ function upgrade_install_remote_plugins(array $installable, $confirmed, $heading
echo $output->heading($heading, 3);
}
echo html_writer::start_tag('pre', array('class' => 'plugin-install-console'));
$validated = $pluginman->install_remote_plugins($installable, false, false);
$validated = $pluginman->install_plugins($installable, false, false);
echo html_writer::end_tag('pre');
if ($validated) {
echo $output->install_remote_plugins_buttons($continue, null, $return);
echo $output->install_plugins_buttons($continue, null, $return);
} else {
echo $output->install_remote_plugins_buttons(null, null, $return);
echo $output->install_plugins_buttons(null, null, $return);
}
echo $output->footer();
die();
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment