MDL-49329 admin: Make plugin manager able to install from local zips too
authorDavid Mudrák <david@moodle.com>
Thu, 8 Oct 2015 19:21:15 +0000 (21:21 +0200)
committerDavid Mudrák <david@moodle.com>
Fri, 9 Oct 2015 07:50:46 +0000 (09:50 +0200)
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
admin/plugins.php
admin/renderer.php
lang/en/plugin.php
lib/classes/plugin_manager.php
lib/classes/update/code_manager.php
lib/tests/update_code_manager_test.php
lib/upgradelib.php

index c5b562d..1aaef9e 100644 (file)
@@ -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)
index 97fd998..ae255e2 100644 (file)
@@ -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)
index 26ca375..f2b3f5c 100644 (file)
@@ -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');
 
index 9e873c2..e054a82 100644 (file)
@@ -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';
index 4de0ae7..c478734 100644 (file)
@@ -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);
index 6d6d320..383890b 100644 (file)
@@ -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.
 
     /**
index e1652c4..ec9401f 100644 (file)
@@ -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));
+    }
+
 }
index fedabf5..c75f28e 100644 (file)
@@ -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();