From 2d00be61f168d5ffb99cba10cc47c30544cd9a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= Date: Thu, 8 Oct 2015 21:21:15 +0200 Subject: [PATCH] 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. --- admin/index.php | 16 +++---- admin/plugins.php | 4 +- admin/renderer.php | 2 +- lang/en/plugin.php | 6 +-- lib/classes/plugin_manager.php | 65 +++++++++++++++++++------- lib/classes/update/code_manager.php | 40 ++++++++++++++++ lib/tests/update_code_manager_test.php | 11 +++++ lib/upgradelib.php | 12 ++--- 8 files changed, 118 insertions(+), 38 deletions(-) diff --git a/admin/index.php b/admin/index.php index c5b562d700c..1aaef9edacf 100644 --- a/admin/index.php +++ b/admin/index.php @@ -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) diff --git a/admin/plugins.php b/admin/plugins.php index 97fd998a0f7..ae255e23d35 100644 --- a/admin/plugins.php +++ b/admin/plugins.php @@ -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) diff --git a/admin/renderer.php b/admin/renderer.php index 26ca375593d..f2b3f5c4523 100644 --- a/admin/renderer.php +++ b/admin/renderer.php @@ -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'); diff --git a/lang/en/plugin.php b/lang/en/plugin.php index 9e873c2f5b1..e054a825a0e 100644 --- a/lang/en/plugin.php +++ b/lang/en/plugin.php @@ -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'; diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index 4de0ae713e1..c4787345c9f 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -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. * - * 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 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. * - * @param array $plugins list of installable remote plugins + * If used for installation of plugins from locally available ZIP files, + * the $plugins should be list of objects with properties ->component and + * ->zipfilepath. + * + * 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); diff --git a/lib/classes/update/code_manager.php b/lib/classes/update/code_manager.php index 6d6d320ef83..383890bbfc7 100644 --- a/lib/classes/update/code_manager.php +++ b/lib/classes/update/code_manager.php @@ -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. /** diff --git a/lib/tests/update_code_manager_test.php b/lib/tests/update_code_manager_test.php index e1652c4fcf7..ec9401f8b55 100644 --- a/lib/tests/update_code_manager_test.php +++ b/lib/tests/update_code_manager_test.php @@ -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)); + } + } diff --git a/lib/upgradelib.php b/lib/upgradelib.php index fedabf51282..c75f28e601e 100644 --- a/lib/upgradelib.php +++ b/lib/upgradelib.php @@ -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();